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.
- {arthexis-0.1.16.dist-info → arthexis-0.1.26.dist-info}/METADATA +84 -35
- arthexis-0.1.26.dist-info/RECORD +111 -0
- config/asgi.py +1 -15
- config/middleware.py +47 -1
- config/settings.py +15 -30
- config/urls.py +53 -1
- core/admin.py +540 -450
- core/apps.py +0 -6
- core/auto_upgrade.py +19 -4
- core/backends.py +13 -3
- core/changelog.py +66 -5
- core/environment.py +4 -5
- core/models.py +1566 -203
- core/notifications.py +1 -1
- core/reference_utils.py +10 -11
- core/release.py +55 -7
- core/sigil_builder.py +2 -2
- core/sigil_resolver.py +1 -66
- core/system.py +268 -2
- core/tasks.py +174 -48
- core/tests.py +314 -16
- core/user_data.py +42 -2
- core/views.py +278 -183
- nodes/admin.py +557 -65
- nodes/apps.py +11 -0
- nodes/models.py +658 -113
- nodes/rfid_sync.py +1 -1
- nodes/tasks.py +97 -2
- nodes/tests.py +1212 -116
- nodes/urls.py +15 -1
- nodes/utils.py +51 -3
- nodes/views.py +1239 -154
- ocpp/admin.py +979 -152
- ocpp/consumers.py +268 -28
- ocpp/models.py +488 -3
- ocpp/network.py +398 -0
- ocpp/store.py +6 -4
- ocpp/tasks.py +296 -2
- ocpp/test_export_import.py +1 -0
- ocpp/test_rfid.py +121 -4
- ocpp/tests.py +950 -11
- ocpp/transactions_io.py +9 -1
- ocpp/urls.py +3 -3
- ocpp/views.py +596 -51
- pages/admin.py +262 -30
- pages/apps.py +35 -0
- pages/context_processors.py +26 -21
- pages/defaults.py +1 -1
- pages/forms.py +31 -8
- pages/middleware.py +6 -2
- pages/models.py +77 -2
- pages/module_defaults.py +5 -5
- pages/site_config.py +137 -0
- pages/tests.py +885 -109
- pages/urls.py +13 -2
- pages/utils.py +70 -0
- pages/views.py +558 -55
- arthexis-0.1.16.dist-info/RECORD +0 -111
- core/workgroup_urls.py +0 -17
- core/workgroup_views.py +0 -94
- {arthexis-0.1.16.dist-info → arthexis-0.1.26.dist-info}/WHEEL +0 -0
- {arthexis-0.1.16.dist-info → arthexis-0.1.26.dist-info}/licenses/LICENSE +0 -0
- {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,
|
|
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
|
|
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
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|