Files
python-tdd/pyswiss/apps/charts/views.py

144 lines
3.9 KiB
Python
Raw Normal View History

from datetime import datetime, timezone
from django.http import HttpResponse, JsonResponse
PICK SKY overlay: D3 natal wheel, Character model, PySwiss aspects+tz PySwiss: - calculate_aspects() in calc.py (conjunction/sextile/square/trine/opposition with orbs) - /api/tz/ endpoint (timezonefinder lat/lon → IANA timezone) - aspects included in /api/chart/ response - timezonefinder==8.2.2 added to requirements - 14 new unit tests (test_calc.py) + 12 new integration tests (TimezoneApiTest, aspect fields) Main app: - Sign, Planet, AspectType, HouseLabel reference models + seeded migrations (0032–0033) - Character model with birth_dt/lat/lon/place, house_system, chart_data, celtic_cross, confirmed_at/retired_at lifecycle (migration 0034) - natus_preview proxy view: calls PySwiss /api/chart/ + optional /api/tz/ auto-resolution, computes planet-in-house distinctions, returns enriched JSON - natus_save view: find-or-create draft Character, confirmed_at on action='confirm' - natus-wheel.js: D3 v7 SVG natal wheel (elements pie, signs, houses, planets, aspects, ASC/MC axes); NatusWheel.draw() / redraw() / clear() - _natus_overlay.html: Nominatim place autocomplete (debounced 400ms), geolocation button with reverse-geocode city name, live chart preview (debounced 300ms), tz auto-fill, NVM / SAVE SKY footer; html.natus-open class toggle pattern - _natus.scss: Gaussian backdrop+modal, two-column form|wheel layout, suggestion dropdown, portrait collapse at 600px, landscape sidebar z-index sink - room.html: include overlay when table_status == SKY_SELECT Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 02:09:26 -04:00
from timezonefinder import TimezoneFinder
import swisseph as swe
from .calc import (
DEFAULT_HOUSE_SYSTEM,
PICK SKY overlay: D3 natal wheel, Character model, PySwiss aspects+tz PySwiss: - calculate_aspects() in calc.py (conjunction/sextile/square/trine/opposition with orbs) - /api/tz/ endpoint (timezonefinder lat/lon → IANA timezone) - aspects included in /api/chart/ response - timezonefinder==8.2.2 added to requirements - 14 new unit tests (test_calc.py) + 12 new integration tests (TimezoneApiTest, aspect fields) Main app: - Sign, Planet, AspectType, HouseLabel reference models + seeded migrations (0032–0033) - Character model with birth_dt/lat/lon/place, house_system, chart_data, celtic_cross, confirmed_at/retired_at lifecycle (migration 0034) - natus_preview proxy view: calls PySwiss /api/chart/ + optional /api/tz/ auto-resolution, computes planet-in-house distinctions, returns enriched JSON - natus_save view: find-or-create draft Character, confirmed_at on action='confirm' - natus-wheel.js: D3 v7 SVG natal wheel (elements pie, signs, houses, planets, aspects, ASC/MC axes); NatusWheel.draw() / redraw() / clear() - _natus_overlay.html: Nominatim place autocomplete (debounced 400ms), geolocation button with reverse-geocode city name, live chart preview (debounced 300ms), tz auto-fill, NVM / SAVE SKY footer; html.natus-open class toggle pattern - _natus.scss: Gaussian backdrop+modal, two-column form|wheel layout, suggestion dropdown, portrait collapse at 600px, landscape sidebar z-index sink - room.html: include overlay when table_status == SKY_SELECT Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 02:09:26 -04:00
calculate_aspects,
get_element_counts,
get_julian_day,
get_planet_positions,
set_ephe_path,
)
from .models import EphemerisSnapshot
def chart(request):
dt_str = request.GET.get('dt')
lat_str = request.GET.get('lat')
lon_str = request.GET.get('lon')
if not dt_str or lat_str is None or lon_str is None:
return HttpResponse(status=400)
try:
dt = datetime.fromisoformat(dt_str.replace('Z', '+00:00'))
except ValueError:
return HttpResponse(status=400)
try:
lat = float(lat_str)
lon = float(lon_str)
except ValueError:
return HttpResponse(status=400)
if not (-90 <= lat <= 90):
return HttpResponse(status=400)
house_system_param = request.GET.get('house_system')
if house_system_param is not None:
if not (hasattr(request, 'user') and request.user.is_authenticated
and request.user.is_superuser):
return HttpResponse(status=403)
house_system = house_system_param
else:
house_system = DEFAULT_HOUSE_SYSTEM
set_ephe_path()
jd = get_julian_day(dt)
planets = get_planet_positions(jd)
cusps, ascmc = swe.houses(jd, lat, lon, house_system.encode())
houses = {
'cusps': list(cusps),
'asc': ascmc[0],
'mc': ascmc[1],
}
return JsonResponse({
'planets': planets,
'houses': houses,
'elements': get_element_counts(planets),
PICK SKY overlay: D3 natal wheel, Character model, PySwiss aspects+tz PySwiss: - calculate_aspects() in calc.py (conjunction/sextile/square/trine/opposition with orbs) - /api/tz/ endpoint (timezonefinder lat/lon → IANA timezone) - aspects included in /api/chart/ response - timezonefinder==8.2.2 added to requirements - 14 new unit tests (test_calc.py) + 12 new integration tests (TimezoneApiTest, aspect fields) Main app: - Sign, Planet, AspectType, HouseLabel reference models + seeded migrations (0032–0033) - Character model with birth_dt/lat/lon/place, house_system, chart_data, celtic_cross, confirmed_at/retired_at lifecycle (migration 0034) - natus_preview proxy view: calls PySwiss /api/chart/ + optional /api/tz/ auto-resolution, computes planet-in-house distinctions, returns enriched JSON - natus_save view: find-or-create draft Character, confirmed_at on action='confirm' - natus-wheel.js: D3 v7 SVG natal wheel (elements pie, signs, houses, planets, aspects, ASC/MC axes); NatusWheel.draw() / redraw() / clear() - _natus_overlay.html: Nominatim place autocomplete (debounced 400ms), geolocation button with reverse-geocode city name, live chart preview (debounced 300ms), tz auto-fill, NVM / SAVE SKY footer; html.natus-open class toggle pattern - _natus.scss: Gaussian backdrop+modal, two-column form|wheel layout, suggestion dropdown, portrait collapse at 600px, landscape sidebar z-index sink - room.html: include overlay when table_status == SKY_SELECT Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 02:09:26 -04:00
'aspects': calculate_aspects(planets),
'house_system': house_system,
})
PICK SKY overlay: D3 natal wheel, Character model, PySwiss aspects+tz PySwiss: - calculate_aspects() in calc.py (conjunction/sextile/square/trine/opposition with orbs) - /api/tz/ endpoint (timezonefinder lat/lon → IANA timezone) - aspects included in /api/chart/ response - timezonefinder==8.2.2 added to requirements - 14 new unit tests (test_calc.py) + 12 new integration tests (TimezoneApiTest, aspect fields) Main app: - Sign, Planet, AspectType, HouseLabel reference models + seeded migrations (0032–0033) - Character model with birth_dt/lat/lon/place, house_system, chart_data, celtic_cross, confirmed_at/retired_at lifecycle (migration 0034) - natus_preview proxy view: calls PySwiss /api/chart/ + optional /api/tz/ auto-resolution, computes planet-in-house distinctions, returns enriched JSON - natus_save view: find-or-create draft Character, confirmed_at on action='confirm' - natus-wheel.js: D3 v7 SVG natal wheel (elements pie, signs, houses, planets, aspects, ASC/MC axes); NatusWheel.draw() / redraw() / clear() - _natus_overlay.html: Nominatim place autocomplete (debounced 400ms), geolocation button with reverse-geocode city name, live chart preview (debounced 300ms), tz auto-fill, NVM / SAVE SKY footer; html.natus-open class toggle pattern - _natus.scss: Gaussian backdrop+modal, two-column form|wheel layout, suggestion dropdown, portrait collapse at 600px, landscape sidebar z-index sink - room.html: include overlay when table_status == SKY_SELECT Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 02:09:26 -04:00
_tf = TimezoneFinder()
def timezone_lookup(request):
"""GET /api/tz/ — resolve IANA timezone string from lat/lon.
Query params: lat (float), lon (float)
Returns: { "timezone": "America/New_York" }
Returns 404 JSON { "timezone": null } if coordinates fall in international
waters (no timezone found) not an error, just no result.
"""
lat_str = request.GET.get('lat')
lon_str = request.GET.get('lon')
if lat_str is None or lon_str is None:
return HttpResponse(status=400)
try:
lat = float(lat_str)
lon = float(lon_str)
except ValueError:
return HttpResponse(status=400)
if not (-90 <= lat <= 90) or not (-180 <= lon <= 180):
return HttpResponse(status=400)
tz = _tf.timezone_at(lat=lat, lng=lon)
return JsonResponse({'timezone': tz})
def charts_list(request):
date_from_str = request.GET.get('date_from')
date_to_str = request.GET.get('date_to')
if not date_from_str or not date_to_str:
return HttpResponse(status=400)
try:
date_from = datetime.strptime(date_from_str, '%Y-%m-%d').replace(
tzinfo=timezone.utc)
date_to = datetime.strptime(date_to_str, '%Y-%m-%d').replace(
hour=23, minute=59, second=59, tzinfo=timezone.utc)
except ValueError:
return HttpResponse(status=400)
if date_to < date_from:
return HttpResponse(status=400)
qs = EphemerisSnapshot.objects.filter(dt__gte=date_from, dt__lte=date_to)
element_fields = {
'fire_min': 'fire', 'water_min': 'water',
'earth_min': 'earth', 'air_min': 'air',
'time_min': 'time_el', 'space_min': 'space_el',
}
for param, field in element_fields.items():
value = request.GET.get(param)
if value is not None:
try:
qs = qs.filter(**{f'{field}__gte': int(value)})
except ValueError:
return HttpResponse(status=400)
results = [
{
'dt': snap.dt.isoformat(),
'elements': snap.elements_dict(),
'planets': snap.chart_data.get('planets', {}),
}
for snap in qs
]
return JsonResponse({'results': results, 'count': len(results)})