sky_natus_data: return stored sky_chart_data (preserves correct asc)
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful

sky_save was re-fetching from PySwiss using sky_birth_dt, which was
stored as local time treated as UTC — giving a different (wrong) asc
than the chart computed by sky_preview. sky_natus_data then served this
wrong chart, rotating the applet wheel by the timezone offset.

Fix: sky_save stores body.get('chart_data') directly (client _lastChartData
is already enriched from sky_preview with correct UTC). sky_natus_data
returns the stored chart with fresh distinctions — no PySwiss call needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-04-21 20:58:16 -04:00
parent b8ac004fb6
commit 3974fdac82
2 changed files with 29 additions and 87 deletions

View File

@@ -529,68 +529,47 @@ class SkySaveViewTest(TestCase):
content_type="application/json",
)
@patch("apps.dashboard.views.http_requests.get")
def test_save_fetches_enriched_data_from_pyswiss(self, mock_get):
mock_resp = MagicMock()
mock_resp.raise_for_status.return_value = None
mock_resp.json.return_value = dict(ENRICHED_CHART)
mock_get.return_value = mock_resp
response = self._post()
self.assertEqual(response.status_code, 200)
self.user.refresh_from_db()
saved = self.user.sky_chart_data
self.assertIsNotNone(saved)
fire = saved["elements"]["Fire"]
self.assertIn("contributors", fire)
self.assertIn("Sun", fire["contributors"])
@patch("apps.dashboard.views.http_requests.get")
def test_save_falls_back_to_client_data_if_pyswiss_unreachable(self, mock_get):
mock_get.side_effect = Exception("connection refused")
client_chart = {"planets": {}, "elements": {"Fire": 2}}
def test_save_stores_client_chart_data(self):
"""sky_save stores the chart_data from the client (already enriched by sky_preview)."""
client_chart = {
"planets": {},
"houses": {"cusps": [float(i * 30) for i in range(12)], "asc": 123.4, "mc": 45.6},
"elements": {"Fire": {"count": 1, "contributors": []}},
}
payload = dict(BIRTH_PAYLOAD, chart_data=client_chart)
response = self._post(payload)
self.assertEqual(response.status_code, 200)
self.user.refresh_from_db()
self.assertEqual(self.user.sky_chart_data, client_chart)
self.assertAlmostEqual(self.user.sky_chart_data["houses"]["asc"], 123.4)
@override_settings(PYSWISS_URL="http://pyswiss-test")
class SkyNatusDataViewTest(TestCase):
def setUp(self):
self.user = User.objects.create(
email="disco@test.io",
sky_birth_lat=51.5,
sky_birth_lon=-0.1,
sky_birth_place="London",
)
self.user = User.objects.create(email="disco@test.io")
self.client.force_login(self.user)
@patch("apps.dashboard.views.http_requests.get")
def test_returns_enriched_chart_data(self, mock_get):
from datetime import datetime, timezone as dt_timezone
self.user.sky_birth_dt = datetime(1990, 6, 15, 12, 0, tzinfo=dt_timezone.utc)
self.user.sky_house_system = "O"
self.user.save(update_fields=["sky_birth_dt", "sky_house_system"])
mock_resp = MagicMock()
mock_resp.raise_for_status.return_value = None
mock_resp.json.return_value = dict(ENRICHED_CHART)
mock_get.return_value = mock_resp
def test_returns_stored_chart_with_asc_preserved(self):
"""sky_natus_data returns sky_chart_data — asc must match what was saved."""
stored = {
"planets": {},
"houses": {"cusps": [float(i * 30) for i in range(12)], "asc": 236.1, "mc": 159.1},
"elements": {"Fire": {"count": 1, "contributors": []}},
"aspects": [],
"house_system": "O",
}
self.user.sky_chart_data = stored
self.user.save(update_fields=["sky_chart_data"])
response = self.client.get("/dashboard/sky/data")
self.assertEqual(response.status_code, 200)
data = json.loads(response.content)
self.assertIn("elements", data)
fire = data["elements"]["Fire"]
self.assertIn("contributors", fire)
self.assertAlmostEqual(data["houses"]["asc"], 236.1)
self.assertIn("distinctions", data)
def test_returns_404_if_no_birth_data_saved(self):
def test_returns_404_if_no_chart_data_saved(self):
response = self.client.get("/dashboard/sky/data")
self.assertEqual(response.status_code, 404)

View File

@@ -327,40 +327,19 @@ def sky_save(request):
user = request.user
birth_dt_str = body.get('birth_dt', '')
birth_dt_utc = None
if birth_dt_str:
try:
naive = datetime.fromisoformat(birth_dt_str.replace('Z', '+00:00'))
user.sky_birth_dt = naive if naive.tzinfo else naive.replace(
tzinfo=zoneinfo.ZoneInfo('UTC')
)
birth_dt_utc = user.sky_birth_dt.astimezone(zoneinfo.ZoneInfo('UTC'))
except ValueError:
user.sky_birth_dt = None
user.sky_birth_lat = body.get('birth_lat')
user.sky_birth_lon = body.get('birth_lon')
user.sky_birth_place = body.get('birth_place', '')
user.sky_house_system = body.get('house_system', 'O')
lat_str = body.get('birth_lat')
lon_str = body.get('birth_lon')
if birth_dt_utc and lat_str is not None and lon_str is not None:
try:
dt_iso = birth_dt_utc.strftime('%Y-%m-%dT%H:%M:%SZ')
resp = http_requests.get(
settings.PYSWISS_URL + '/api/chart/',
params={'dt': dt_iso, 'lat': str(lat_str), 'lon': str(lon_str)},
timeout=5,
)
resp.raise_for_status()
enriched = resp.json()
if 'elements' in enriched and 'Earth' in enriched['elements']:
enriched['elements']['Stone'] = enriched['elements'].pop('Earth')
user.sky_chart_data = enriched
except Exception:
user.sky_chart_data = body.get('chart_data')
else:
user.sky_chart_data = body.get('chart_data')
user.sky_chart_data = body.get('chart_data')
user.save(update_fields=[
'sky_birth_dt', 'sky_birth_lat', 'sky_birth_lon',
@@ -372,27 +351,11 @@ def sky_save(request):
@login_required(login_url="/")
def sky_natus_data(request):
user = request.user
if not user.sky_birth_lat or not user.sky_birth_lon or not user.sky_birth_dt:
if not user.sky_chart_data:
return HttpResponse(status=404)
try:
utc_dt = user.sky_birth_dt.astimezone(zoneinfo.ZoneInfo('UTC'))
dt_iso = utc_dt.strftime('%Y-%m-%dT%H:%M:%SZ')
resp = http_requests.get(
settings.PYSWISS_URL + '/api/chart/',
params={
'dt': dt_iso,
'lat': str(user.sky_birth_lat),
'lon': str(user.sky_birth_lon),
},
timeout=5,
)
resp.raise_for_status()
except Exception:
return HttpResponse(status=502)
data = resp.json()
if 'elements' in data and 'Earth' in data['elements']:
data['elements']['Stone'] = data['elements'].pop('Earth')
data['distinctions'] = _compute_distinctions(data['planets'], data['houses'])
data = dict(user.sky_chart_data)
data['distinctions'] = _compute_distinctions(
data.get('planets', {}), data.get('houses', {})
)
return JsonResponse(data)