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
nodes/rfid_sync.py CHANGED
@@ -99,7 +99,7 @@ def apply_rfid_payload(
99
99
  last_seen = entry.get("last_seen_on")
100
100
  defaults["last_seen_on"] = parse_datetime(last_seen) if last_seen else None
101
101
 
102
- obj, created = RFID.objects.update_or_create(rfid=rfid_value, defaults=defaults)
102
+ obj, created = RFID.update_or_create_from_code(rfid_value, defaults=defaults)
103
103
 
104
104
  outcome.instance = obj
105
105
  outcome.created = created
nodes/tasks.py CHANGED
@@ -1,11 +1,16 @@
1
+ import base64
2
+ import json
1
3
  import logging
2
4
  from pathlib import Path
3
5
 
4
6
  import pyperclip
5
- from pyperclip import PyperclipException
7
+ import requests
6
8
  from celery import shared_task
9
+ from cryptography.hazmat.primitives import hashes, serialization
10
+ from cryptography.hazmat.primitives.asymmetric import padding
11
+ from pyperclip import PyperclipException
7
12
 
8
- from .models import ContentSample, Node
13
+ from .models import ContentSample, NetMessage, Node
9
14
  from .utils import capture_screenshot, save_screenshot
10
15
 
11
16
  logger = logging.getLogger(__name__)
@@ -44,3 +49,93 @@ def capture_node_screenshot(
44
49
  node = Node.get_local()
45
50
  save_screenshot(path, node=node, method=method)
46
51
  return str(path)
52
+
53
+
54
+ @shared_task
55
+ def poll_unreachable_upstream() -> None:
56
+ """Poll upstream nodes for queued NetMessages."""
57
+
58
+ local = Node.get_local()
59
+ if not local or not local.has_feature("celery-queue"):
60
+ return
61
+
62
+ private_key = local.get_private_key()
63
+ if not private_key:
64
+ logger.warning("Node %s cannot sign upstream polls", getattr(local, "pk", None))
65
+ return
66
+
67
+ requester_payload = {"requester": str(local.uuid)}
68
+ payload_json = json.dumps(requester_payload, separators=(",", ":"), sort_keys=True)
69
+ try:
70
+ signature = base64.b64encode(
71
+ private_key.sign(
72
+ payload_json.encode(),
73
+ padding.PKCS1v15(),
74
+ hashes.SHA256(),
75
+ )
76
+ ).decode()
77
+ except Exception as exc:
78
+ logger.warning("Failed to sign upstream poll request: %s", exc)
79
+ return
80
+
81
+ headers = {"Content-Type": "application/json", "X-Signature": signature}
82
+ upstream_nodes = Node.objects.filter(current_relation=Node.Relation.UPSTREAM)
83
+ for upstream in upstream_nodes:
84
+ if not upstream.public_key:
85
+ continue
86
+ response = None
87
+ for url in upstream.iter_remote_urls("/nodes/net-message/pull/"):
88
+ try:
89
+ response = requests.post(
90
+ url, data=payload_json, headers=headers, timeout=5
91
+ )
92
+ except Exception as exc:
93
+ logger.warning("Polling upstream node %s via %s failed: %s", upstream.pk, url, exc)
94
+ continue
95
+ if response.ok:
96
+ break
97
+ logger.warning(
98
+ "Upstream node %s returned status %s", upstream.pk, response.status_code
99
+ )
100
+ response = None
101
+ if response is None or not response.ok:
102
+ continue
103
+ try:
104
+ body = response.json()
105
+ except ValueError:
106
+ logger.warning("Upstream node %s returned invalid JSON", upstream.pk)
107
+ continue
108
+ messages = body.get("messages", [])
109
+ if not isinstance(messages, list) or not messages:
110
+ continue
111
+ try:
112
+ public_key = serialization.load_pem_public_key(upstream.public_key.encode())
113
+ except Exception:
114
+ logger.warning("Upstream node %s has invalid public key", upstream.pk)
115
+ continue
116
+ for item in messages:
117
+ if not isinstance(item, dict):
118
+ continue
119
+ payload = item.get("payload")
120
+ payload_signature = item.get("signature")
121
+ if not isinstance(payload, dict) or not payload_signature:
122
+ continue
123
+ payload_text = json.dumps(payload, separators=(",", ":"), sort_keys=True)
124
+ try:
125
+ public_key.verify(
126
+ base64.b64decode(payload_signature),
127
+ payload_text.encode(),
128
+ padding.PKCS1v15(),
129
+ hashes.SHA256(),
130
+ )
131
+ except Exception:
132
+ logger.warning(
133
+ "Signature verification failed for upstream node %s", upstream.pk
134
+ )
135
+ continue
136
+ try:
137
+ NetMessage.receive_payload(payload, sender=upstream)
138
+ except ValueError as exc:
139
+ logger.warning(
140
+ "Discarded upstream message from node %s: %s", upstream.pk, exc
141
+ )