arthexis 0.1.16__py3-none-any.whl → 0.1.26__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of arthexis might be problematic. Click here for more details.

Files changed (63) hide show
  1. {arthexis-0.1.16.dist-info → arthexis-0.1.26.dist-info}/METADATA +84 -35
  2. arthexis-0.1.26.dist-info/RECORD +111 -0
  3. config/asgi.py +1 -15
  4. config/middleware.py +47 -1
  5. config/settings.py +15 -30
  6. config/urls.py +53 -1
  7. core/admin.py +540 -450
  8. core/apps.py +0 -6
  9. core/auto_upgrade.py +19 -4
  10. core/backends.py +13 -3
  11. core/changelog.py +66 -5
  12. core/environment.py +4 -5
  13. core/models.py +1566 -203
  14. core/notifications.py +1 -1
  15. core/reference_utils.py +10 -11
  16. core/release.py +55 -7
  17. core/sigil_builder.py +2 -2
  18. core/sigil_resolver.py +1 -66
  19. core/system.py +268 -2
  20. core/tasks.py +174 -48
  21. core/tests.py +314 -16
  22. core/user_data.py +42 -2
  23. core/views.py +278 -183
  24. nodes/admin.py +557 -65
  25. nodes/apps.py +11 -0
  26. nodes/models.py +658 -113
  27. nodes/rfid_sync.py +1 -1
  28. nodes/tasks.py +97 -2
  29. nodes/tests.py +1212 -116
  30. nodes/urls.py +15 -1
  31. nodes/utils.py +51 -3
  32. nodes/views.py +1239 -154
  33. ocpp/admin.py +979 -152
  34. ocpp/consumers.py +268 -28
  35. ocpp/models.py +488 -3
  36. ocpp/network.py +398 -0
  37. ocpp/store.py +6 -4
  38. ocpp/tasks.py +296 -2
  39. ocpp/test_export_import.py +1 -0
  40. ocpp/test_rfid.py +121 -4
  41. ocpp/tests.py +950 -11
  42. ocpp/transactions_io.py +9 -1
  43. ocpp/urls.py +3 -3
  44. ocpp/views.py +596 -51
  45. pages/admin.py +262 -30
  46. pages/apps.py +35 -0
  47. pages/context_processors.py +26 -21
  48. pages/defaults.py +1 -1
  49. pages/forms.py +31 -8
  50. pages/middleware.py +6 -2
  51. pages/models.py +77 -2
  52. pages/module_defaults.py +5 -5
  53. pages/site_config.py +137 -0
  54. pages/tests.py +885 -109
  55. pages/urls.py +13 -2
  56. pages/utils.py +70 -0
  57. pages/views.py +558 -55
  58. arthexis-0.1.16.dist-info/RECORD +0 -111
  59. core/workgroup_urls.py +0 -17
  60. core/workgroup_views.py +0 -94
  61. {arthexis-0.1.16.dist-info → arthexis-0.1.26.dist-info}/WHEEL +0 -0
  62. {arthexis-0.1.16.dist-info → arthexis-0.1.26.dist-info}/licenses/LICENSE +0 -0
  63. {arthexis-0.1.16.dist-info → arthexis-0.1.26.dist-info}/top_level.txt +0 -0
core/tests.py CHANGED
@@ -8,6 +8,7 @@ django.setup()
8
8
  from django.test import Client, TestCase, RequestFactory, override_settings
9
9
  from django.urls import reverse
10
10
  from django.http import HttpRequest
11
+ from django.contrib import messages
11
12
  import csv
12
13
  import json
13
14
  import importlib.util
@@ -20,6 +21,7 @@ import types
20
21
  from glob import glob
21
22
  from datetime import datetime, timedelta, timezone as datetime_timezone
22
23
  import tempfile
24
+ from io import StringIO
23
25
  from urllib.parse import quote
24
26
 
25
27
  from django.utils import timezone
@@ -57,6 +59,7 @@ from nodes.models import ContentSample
57
59
 
58
60
  from django.core.exceptions import ValidationError
59
61
  from django.core.management import call_command
62
+ from django.core.management.base import CommandError
60
63
  from django.db import IntegrityError
61
64
  from .backends import LocalhostAdminBackend
62
65
  from core.views import (
@@ -548,6 +551,15 @@ class RFIDValidationTests(TestCase):
548
551
  tag = RFID.objects.create(rfid="DEADBEEF10")
549
552
  self.assertEqual(tag.rfid, "DEADBEEF10")
550
553
 
554
+ def test_reversed_uid_updates_with_rfid(self):
555
+ tag = RFID.objects.create(rfid="A1B2C3D4")
556
+ self.assertEqual(tag.reversed_uid, "D4C3B2A1")
557
+
558
+ tag.rfid = "112233"
559
+ tag.save(update_fields=["rfid"])
560
+ tag.refresh_from_db()
561
+ self.assertEqual(tag.reversed_uid, "332211")
562
+
551
563
  def test_find_user_by_rfid(self):
552
564
  user = User.objects.create_user(username="finder", password="pwd")
553
565
  acc = EnergyAccount.objects.create(user=user, name="FINDER")
@@ -983,6 +995,40 @@ class RFIDImportExportCommandTests(TestCase):
983
995
  self.assertTrue(tag.energy_accounts.filter(pk=account.pk).exists())
984
996
 
985
997
 
998
+ class CheckRFIDCommandTests(TestCase):
999
+ def test_successful_validation_outputs_json(self):
1000
+ out = StringIO()
1001
+
1002
+ call_command("check_rfid", "abcd1234", stdout=out)
1003
+
1004
+ payload = json.loads(out.getvalue())
1005
+ self.assertEqual(payload["rfid"], "ABCD1234")
1006
+ self.assertTrue(payload["created"])
1007
+ self.assertTrue(RFID.objects.filter(rfid="ABCD1234").exists())
1008
+
1009
+ def test_invalid_value_raises_error(self):
1010
+ with self.assertRaises(CommandError):
1011
+ call_command("check_rfid", "invalid!")
1012
+
1013
+ def test_kind_option_updates_existing_tag(self):
1014
+ tag = RFID.objects.create(rfid="EXISTING", allowed=False, kind=RFID.CLASSIC)
1015
+ out = StringIO()
1016
+
1017
+ call_command(
1018
+ "check_rfid",
1019
+ "existing",
1020
+ "--kind",
1021
+ RFID.NTAG215,
1022
+ stdout=out,
1023
+ )
1024
+
1025
+ payload = json.loads(out.getvalue())
1026
+ tag.refresh_from_db()
1027
+ self.assertFalse(payload["created"])
1028
+ self.assertEqual(payload["kind"], RFID.NTAG215)
1029
+ self.assertEqual(tag.kind, RFID.NTAG215)
1030
+
1031
+
986
1032
  class RFIDKeyVerificationFlagTests(TestCase):
987
1033
  def test_flags_reset_on_key_change(self):
988
1034
  tag = RFID.objects.create(
@@ -1037,6 +1083,44 @@ class ReleaseProcessTests(TestCase):
1037
1083
  )
1038
1084
  sync_main.assert_called_once_with(Path("rel.log"))
1039
1085
 
1086
+ def test_step_check_todos_logs_instruction_when_pending(self):
1087
+ log_path = Path("rel.log")
1088
+ log_path.unlink(missing_ok=True)
1089
+ Todo.objects.create(request="Review checklist")
1090
+ ctx: dict[str, object] = {}
1091
+
1092
+ try:
1093
+ with self.assertRaises(core_views.PendingTodos):
1094
+ core_views._step_check_todos(self.release, ctx, log_path)
1095
+
1096
+ contents = log_path.read_text(encoding="utf-8")
1097
+ message = "Release checklist requires acknowledgment before continuing."
1098
+ self.assertIn(message, contents)
1099
+ self.assertIn("Review outstanding TODO items", contents)
1100
+
1101
+ with self.assertRaises(core_views.PendingTodos):
1102
+ core_views._step_check_todos(self.release, ctx, log_path)
1103
+
1104
+ contents = log_path.read_text(encoding="utf-8")
1105
+ self.assertEqual(contents.count(message), 1)
1106
+ finally:
1107
+ log_path.unlink(missing_ok=True)
1108
+
1109
+ def test_step_check_todos_auto_ack_when_no_pending(self):
1110
+ log_path = Path("rel.log")
1111
+ log_path.unlink(missing_ok=True)
1112
+ ctx: dict[str, object] = {}
1113
+
1114
+ try:
1115
+ with mock.patch("core.views._refresh_changelog_once"):
1116
+ core_views._step_check_todos(self.release, ctx, log_path)
1117
+ finally:
1118
+ log_path.unlink(missing_ok=True)
1119
+
1120
+ self.assertTrue(ctx.get("todos_ack"))
1121
+ self.assertNotIn("todos_required", ctx)
1122
+ self.assertIsNone(ctx.get("todos"))
1123
+
1040
1124
  @mock.patch("core.views._sync_with_origin_main")
1041
1125
  @mock.patch("core.views.release_utils._git_clean", return_value=True)
1042
1126
  @mock.patch("core.views.release_utils.network_available", return_value=False)
@@ -1206,11 +1290,10 @@ class ReleaseProcessTests(TestCase):
1206
1290
  run.assert_any_call(["git", "clean", "-fd"], check=False)
1207
1291
 
1208
1292
  @mock.patch("core.views.PackageRelease.dump_fixture")
1209
- @mock.patch("core.views._ensure_release_todo")
1210
1293
  @mock.patch("core.views._sync_with_origin_main")
1211
1294
  @mock.patch("core.views.subprocess.run")
1212
1295
  def test_pre_release_syncs_with_main(
1213
- self, run, sync_main, ensure_todo, dump_fixture
1296
+ self, run, sync_main, dump_fixture
1214
1297
  ):
1215
1298
  import subprocess as sp
1216
1299
 
@@ -1222,11 +1305,6 @@ class ReleaseProcessTests(TestCase):
1222
1305
  return sp.CompletedProcess(cmd, 0)
1223
1306
 
1224
1307
  run.side_effect = fake_run
1225
- ensure_todo.return_value = (
1226
- mock.Mock(request="Create release pkg 1.0.1", url="", request_details=""),
1227
- Path("core/fixtures/todos__next_release.json"),
1228
- )
1229
-
1230
1308
  version_path = Path("VERSION")
1231
1309
  original_version = version_path.read_text(encoding="utf-8")
1232
1310
 
@@ -1527,6 +1605,54 @@ class ReleaseProcessTests(TestCase):
1527
1605
  ctx = session.get(f"release_publish_{self.release.pk}")
1528
1606
  self.assertTrue(ctx.get("dry_run"))
1529
1607
 
1608
+ def test_resume_button_shown_when_credentials_missing(self):
1609
+ user = User.objects.create_superuser("admin", "admin@example.com", "pw")
1610
+ url = reverse("release-progress", args=[self.release.pk, "publish"])
1611
+ self.client.force_login(user)
1612
+
1613
+ self.client.get(f"{url}?start=1")
1614
+
1615
+ session = self.client.session
1616
+ ctx = session.get(f"release_publish_{self.release.pk}") or {}
1617
+ ctx.update({"step": 7, "started": True, "paused": False})
1618
+ session[f"release_publish_{self.release.pk}"] = ctx
1619
+ session.save()
1620
+
1621
+ response = self.client.get(f"{url}?step=7")
1622
+ self.assertEqual(response.status_code, 200)
1623
+ context = response.context
1624
+ if isinstance(context, list):
1625
+ context = context[-1]
1626
+ self.assertTrue(context["resume_available"])
1627
+ self.assertIn(b"Resume Publish", response.content)
1628
+
1629
+ def test_resume_without_step_parameter_defaults_to_current_progress(self):
1630
+ run: list[str] = []
1631
+
1632
+ def step_fn(release, ctx, log_path):
1633
+ run.append("step")
1634
+
1635
+ steps = [("Only step", step_fn)]
1636
+ user = User.objects.create_superuser("admin", "admin@example.com", "pw")
1637
+ url = reverse("release-progress", args=[self.release.pk, "publish"])
1638
+ with mock.patch("core.views.PUBLISH_STEPS", steps):
1639
+ self.client.force_login(user)
1640
+ session = self.client.session
1641
+ session[f"release_publish_{self.release.pk}"] = {
1642
+ "step": 0,
1643
+ "started": True,
1644
+ "paused": False,
1645
+ }
1646
+ session.save()
1647
+
1648
+ response = self.client.get(f"{url}?resume=1")
1649
+ self.assertEqual(response.status_code, 200)
1650
+ self.assertEqual(run, ["step"])
1651
+
1652
+ session = self.client.session
1653
+ ctx = session.get(f"release_publish_{self.release.pk}")
1654
+ self.assertEqual(ctx.get("step"), 1)
1655
+
1530
1656
  def test_new_todo_does_not_reset_pending_flow(self):
1531
1657
  user = User.objects.create_superuser("admin", "admin@example.com", "pw")
1532
1658
  url = reverse("release-progress", args=[self.release.pk, "publish"])
@@ -1843,7 +1969,7 @@ class PackageReleaseAdminActionTests(TestCase):
1843
1969
 
1844
1970
  @mock.patch("core.admin.PackageRelease.dump_fixture")
1845
1971
  @mock.patch("core.admin.requests.get")
1846
- def test_refresh_from_pypi_creates_releases(self, mock_get, dump):
1972
+ def test_refresh_from_pypi_reports_missing_releases(self, mock_get, dump):
1847
1973
  mock_get.return_value.raise_for_status.return_value = None
1848
1974
  mock_get.return_value.json.return_value = {
1849
1975
  "releases": {
@@ -1856,13 +1982,17 @@ class PackageReleaseAdminActionTests(TestCase):
1856
1982
  }
1857
1983
  }
1858
1984
  self.admin.refresh_from_pypi(self.request, PackageRelease.objects.none())
1859
- new_release = PackageRelease.objects.get(version="1.1.0")
1860
- self.assertEqual(new_release.revision, "")
1861
- self.assertEqual(
1862
- new_release.release_on,
1863
- datetime(2024, 2, 2, 15, 45, tzinfo=datetime_timezone.utc),
1985
+ self.assertFalse(
1986
+ PackageRelease.objects.filter(version="1.1.0").exists()
1987
+ )
1988
+ dump.assert_not_called()
1989
+ self.assertIn(
1990
+ (
1991
+ "Manual creation required for 1 release: 1.1.0",
1992
+ messages.WARNING,
1993
+ ),
1994
+ self.messages,
1864
1995
  )
1865
- dump.assert_called_once()
1866
1996
 
1867
1997
  @mock.patch("core.admin.PackageRelease.dump_fixture")
1868
1998
  @mock.patch("core.admin.requests.get")
@@ -2008,6 +2138,25 @@ class PackageReleaseCurrentTests(TestCase):
2008
2138
  self.package.save()
2009
2139
  self.assertFalse(self.release.is_current)
2010
2140
 
2141
+ def test_is_current_false_when_version_has_plus(self):
2142
+ self.version_path.write_text("1.0.0+")
2143
+ self.assertFalse(self.release.is_current)
2144
+
2145
+
2146
+ class PackageReleaseRevisionTests(TestCase):
2147
+ def setUp(self):
2148
+ self.package = Package.objects.get(name="arthexis")
2149
+ self.release = PackageRelease.objects.create(
2150
+ package=self.package,
2151
+ version="1.0.0",
2152
+ revision="abcdef123456",
2153
+ )
2154
+
2155
+ def test_matches_revision_ignores_plus_suffix(self):
2156
+ self.assertTrue(
2157
+ PackageRelease.matches_revision("1.0.0+", "abcdef123456")
2158
+ )
2159
+
2011
2160
  def test_is_current_false_when_version_differs(self):
2012
2161
  self.release.version = "2.0.0"
2013
2162
  self.release.save()
@@ -2058,13 +2207,22 @@ class PackageAdminPrepareNextReleaseTests(TestCase):
2058
2207
 
2059
2208
  def test_prepare_next_release_active_creates_release(self):
2060
2209
  PackageRelease.all_objects.filter(package=self.package).delete()
2061
- request = self.factory.get("/admin/core/package/prepare-next-release/")
2210
+ request = self.factory.post("/admin/core/package/prepare-next-release/")
2062
2211
  response = self.admin.prepare_next_release_active(request)
2063
2212
  self.assertEqual(response.status_code, 302)
2064
2213
  self.assertEqual(
2065
2214
  PackageRelease.all_objects.filter(package=self.package).count(), 1
2066
2215
  )
2067
2216
 
2217
+ def test_prepare_next_release_active_get_creates_release(self):
2218
+ PackageRelease.all_objects.filter(package=self.package).delete()
2219
+ request = self.factory.get("/admin/core/package/prepare-next-release/")
2220
+ response = self.admin.prepare_next_release_active(request)
2221
+ self.assertEqual(response.status_code, 302)
2222
+ self.assertTrue(
2223
+ PackageRelease.all_objects.filter(package=self.package).exists()
2224
+ )
2225
+
2068
2226
 
2069
2227
  class PackageAdminChangeViewTests(TestCase):
2070
2228
  def setUp(self):
@@ -2086,13 +2244,97 @@ class TodoDoneTests(TestCase):
2086
2244
  User.objects.create_superuser("admin", "admin@example.com", "pw")
2087
2245
  self.client.force_login(User.objects.get(username="admin"))
2088
2246
 
2089
- def test_mark_done_sets_timestamp(self):
2247
+ @mock.patch("core.models.revision_utils.get_revision", return_value="rev123")
2248
+ def test_mark_done_sets_timestamp(self, _get_revision):
2090
2249
  todo = Todo.objects.create(request="Task", is_seed_data=True)
2091
2250
  resp = self.client.post(reverse("todo-done", args=[todo.pk]))
2092
2251
  self.assertRedirects(resp, reverse("admin:index"))
2093
2252
  todo.refresh_from_db()
2094
2253
  self.assertIsNotNone(todo.done_on)
2095
2254
  self.assertFalse(todo.is_deleted)
2255
+ self.assertIsNone(todo.done_node)
2256
+ version_path = Path(settings.BASE_DIR) / "VERSION"
2257
+ expected_version = ""
2258
+ if version_path.exists():
2259
+ expected_version = version_path.read_text(encoding="utf-8").strip()
2260
+ self.assertEqual(todo.done_version, expected_version)
2261
+ self.assertEqual(todo.done_revision, "rev123")
2262
+ self.assertEqual(todo.done_username, "admin")
2263
+
2264
+ def test_mark_done_updates_seed_fixture(self):
2265
+ todo = Todo.objects.create(request="Task", is_seed_data=True)
2266
+ with tempfile.TemporaryDirectory() as tmp:
2267
+ base = Path(tmp)
2268
+ fixture_dir = base / "core" / "fixtures"
2269
+ fixture_dir.mkdir(parents=True)
2270
+ fixture_path = fixture_dir / "todo__task.json"
2271
+ fixture_path.write_text(
2272
+ json.dumps(
2273
+ [
2274
+ {
2275
+ "model": "core.todo",
2276
+ "fields": {
2277
+ "request": "Task",
2278
+ "url": "",
2279
+ "request_details": "",
2280
+ },
2281
+ }
2282
+ ],
2283
+ indent=2,
2284
+ )
2285
+ + "\n",
2286
+ encoding="utf-8",
2287
+ )
2288
+
2289
+ with override_settings(BASE_DIR=base):
2290
+ with mock.patch(
2291
+ "core.models.revision_utils.get_revision", return_value="rev456"
2292
+ ):
2293
+ resp = self.client.post(reverse("todo-done", args=[todo.pk]))
2294
+
2295
+ self.assertRedirects(resp, reverse("admin:index"))
2296
+ data = json.loads(fixture_path.read_text(encoding="utf-8"))
2297
+ self.assertEqual(len(data), 1)
2298
+ fields = data[0]["fields"]
2299
+ self.assertIn("done_on", fields)
2300
+ self.assertTrue(fields["done_on"])
2301
+ self.assertFalse(fields.get("is_deleted", False))
2302
+ self.assertIn("done_version", fields)
2303
+ self.assertEqual(fields.get("done_revision"), "rev456")
2304
+ self.assertEqual(fields.get("done_username"), "admin")
2305
+
2306
+ def test_soft_delete_updates_seed_fixture(self):
2307
+ todo = Todo.objects.create(request="Task", is_seed_data=True)
2308
+ with tempfile.TemporaryDirectory() as tmp:
2309
+ base = Path(tmp)
2310
+ fixture_dir = base / "core" / "fixtures"
2311
+ fixture_dir.mkdir(parents=True)
2312
+ fixture_path = fixture_dir / "todo__task.json"
2313
+ fixture_path.write_text(
2314
+ json.dumps(
2315
+ [
2316
+ {
2317
+ "model": "core.todo",
2318
+ "fields": {
2319
+ "request": "Task",
2320
+ "url": "",
2321
+ "request_details": "",
2322
+ },
2323
+ }
2324
+ ],
2325
+ indent=2,
2326
+ )
2327
+ + "\n",
2328
+ encoding="utf-8",
2329
+ )
2330
+
2331
+ with override_settings(BASE_DIR=base):
2332
+ todo.delete()
2333
+
2334
+ data = json.loads(fixture_path.read_text(encoding="utf-8"))
2335
+ self.assertEqual(len(data), 1)
2336
+ fields = data[0]["fields"]
2337
+ self.assertTrue(fields.get("is_deleted"))
2096
2338
 
2097
2339
  def test_mark_done_missing_task_refreshes(self):
2098
2340
  todo = Todo.objects.create(request="Task", is_seed_data=True)
@@ -2198,6 +2440,61 @@ class TodoDoneTests(TestCase):
2198
2440
  self.assertTrue(todo.is_seed_data)
2199
2441
 
2200
2442
 
2443
+ class TodoDeleteTests(TestCase):
2444
+ def setUp(self):
2445
+ self.client = Client()
2446
+ User.objects.create_superuser("admin", "admin@example.com", "pw")
2447
+ self.client.force_login(User.objects.get(username="admin"))
2448
+
2449
+ def test_delete_marks_task_deleted(self):
2450
+ todo = Todo.objects.create(request="Task", is_seed_data=True)
2451
+ resp = self.client.post(reverse("todo-delete", args=[todo.pk]))
2452
+ self.assertRedirects(resp, reverse("admin:index"))
2453
+ todo.refresh_from_db()
2454
+ self.assertTrue(todo.is_deleted)
2455
+ self.assertIsNone(todo.done_on)
2456
+
2457
+ def test_delete_updates_seed_fixture(self):
2458
+ todo = Todo.objects.create(request="Task", is_seed_data=True)
2459
+ with tempfile.TemporaryDirectory() as tmp:
2460
+ base = Path(tmp)
2461
+ fixture_dir = base / "core" / "fixtures"
2462
+ fixture_dir.mkdir(parents=True)
2463
+ fixture_path = fixture_dir / "todo__task.json"
2464
+ fixture_path.write_text(
2465
+ json.dumps(
2466
+ [
2467
+ {
2468
+ "model": "core.todo",
2469
+ "fields": {
2470
+ "request": "Task",
2471
+ "url": "",
2472
+ "request_details": "",
2473
+ },
2474
+ }
2475
+ ],
2476
+ indent=2,
2477
+ )
2478
+ + "\n",
2479
+ encoding="utf-8",
2480
+ )
2481
+
2482
+ with override_settings(BASE_DIR=base):
2483
+ resp = self.client.post(reverse("todo-delete", args=[todo.pk]))
2484
+ self.assertRedirects(resp, reverse("admin:index"))
2485
+ data = json.loads(fixture_path.read_text(encoding="utf-8"))
2486
+ self.assertEqual(len(data), 1)
2487
+ fields = data[0]["fields"]
2488
+ self.assertTrue(fields.get("is_deleted"))
2489
+
2490
+ def test_delete_missing_task_redirects(self):
2491
+ todo = Todo.objects.create(request="Task")
2492
+ todo.is_deleted = True
2493
+ todo.save(update_fields=["is_deleted"])
2494
+ resp = self.client.post(reverse("todo-delete", args=[todo.pk]))
2495
+ self.assertRedirects(resp, reverse("admin:index"))
2496
+
2497
+
2201
2498
  class TodoFocusViewTests(TestCase):
2202
2499
  def setUp(self):
2203
2500
  self.client = Client()
@@ -2215,6 +2512,7 @@ class TodoFocusViewTests(TestCase):
2215
2512
  self.assertEqual(resp["X-Frame-Options"], "SAMEORIGIN")
2216
2513
  self.assertContains(resp, f'src="{todo.url}"')
2217
2514
  self.assertContains(resp, "Done")
2515
+ self.assertContains(resp, "Delete")
2218
2516
  self.assertContains(resp, "Back")
2219
2517
  self.assertContains(resp, "Take Snapshot")
2220
2518
  snapshot_url = reverse("todo-snapshot", args=[todo.pk])
core/user_data.py CHANGED
@@ -201,9 +201,49 @@ def dump_user_fixture(instance, user=None) -> None:
201
201
 
202
202
  def delete_user_fixture(instance, user=None) -> None:
203
203
  target_user = user or _resolve_fixture_user(instance)
204
- if target_user is None:
204
+ filename = (
205
+ f"{instance._meta.app_label}_{instance._meta.model_name}_{instance.pk}.json"
206
+ )
207
+
208
+ def _remove_for_user(candidate) -> None:
209
+ if candidate is None:
210
+ return
211
+ base_path = Path(
212
+ getattr(candidate, "data_path", "") or Path(settings.BASE_DIR) / "data"
213
+ )
214
+ username = _username_for(candidate)
215
+ if not username:
216
+ return
217
+ user_dir = base_path / username
218
+ if user_dir.exists():
219
+ (user_dir / filename).unlink(missing_ok=True)
220
+
221
+ if target_user is not None:
222
+ _remove_for_user(target_user)
205
223
  return
206
- _fixture_path(target_user, instance).unlink(missing_ok=True)
224
+
225
+ root = Path(settings.BASE_DIR) / "data"
226
+ if root.exists():
227
+ (root / filename).unlink(missing_ok=True)
228
+ for path in root.iterdir():
229
+ if path.is_dir():
230
+ (path / filename).unlink(missing_ok=True)
231
+
232
+ UserModel = get_user_model()
233
+ manager = getattr(UserModel, "all_objects", UserModel._default_manager)
234
+ for candidate in manager.all():
235
+ data_path = getattr(candidate, "data_path", "")
236
+ if not data_path:
237
+ continue
238
+ base_path = Path(data_path)
239
+ if not base_path.exists():
240
+ continue
241
+ username = _username_for(candidate)
242
+ if not username:
243
+ continue
244
+ user_dir = base_path / username
245
+ if user_dir.exists():
246
+ (user_dir / filename).unlink(missing_ok=True)
207
247
 
208
248
 
209
249
  def _mark_fixture_user_data(path: Path) -> None: