sky: store birth_tz, prefill form from User model, drop localStorage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-04-21 21:54:34 -04:00
parent b5a92ddf77
commit 5c05bd6552
4 changed files with 41 additions and 45 deletions

View File

@@ -303,9 +303,12 @@ def sky_view(request):
return render(request, "apps/dashboard/sky.html", { return render(request, "apps/dashboard/sky.html", {
"preview_url": request.build_absolute_uri("/dashboard/sky/preview"), "preview_url": request.build_absolute_uri("/dashboard/sky/preview"),
"save_url": request.build_absolute_uri("/dashboard/sky/save"), "save_url": request.build_absolute_uri("/dashboard/sky/save"),
"saved_sky": request.user.sky_chart_data, "saved_sky": request.user.sky_chart_data,
"saved_birth_dt": request.user.sky_birth_dt, "saved_birth_dt": request.user.sky_birth_dt,
"saved_birth_place": request.user.sky_birth_place, "saved_birth_place": request.user.sky_birth_place,
"saved_birth_lat": request.user.sky_birth_lat,
"saved_birth_lon": request.user.sky_birth_lon,
"saved_birth_tz": request.user.sky_birth_tz,
"page_class": "page-sky", "page_class": "page-sky",
}) })
@@ -338,12 +341,13 @@ def sky_save(request):
user.sky_birth_lat = body.get('birth_lat') user.sky_birth_lat = body.get('birth_lat')
user.sky_birth_lon = body.get('birth_lon') user.sky_birth_lon = body.get('birth_lon')
user.sky_birth_place = body.get('birth_place', '') user.sky_birth_place = body.get('birth_place', '')
user.sky_birth_tz = body.get('birth_tz', '')
user.sky_house_system = body.get('house_system', 'O') user.sky_house_system = body.get('house_system', 'O')
user.sky_chart_data = body.get('chart_data') user.sky_chart_data = body.get('chart_data')
user.save(update_fields=[ user.save(update_fields=[
'sky_birth_dt', 'sky_birth_lat', 'sky_birth_lon', 'sky_birth_dt', 'sky_birth_lat', 'sky_birth_lon',
'sky_birth_place', 'sky_house_system', 'sky_chart_data', 'sky_birth_place', 'sky_birth_tz', 'sky_house_system', 'sky_chart_data',
]) ])
return JsonResponse({"saved": True}) return JsonResponse({"saved": True})

View File

@@ -0,0 +1,18 @@
# Generated by Django 6.0 on 2026-04-22 01:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('lyric', '0018_user_sky_fields'),
]
operations = [
migrations.AddField(
model_name='user',
name='sky_birth_tz',
field=models.CharField(blank=True, max_length=64),
),
]

View File

@@ -52,6 +52,7 @@ class User(AbstractBaseUser):
sky_birth_lat = models.DecimalField(max_digits=9, decimal_places=4, null=True, blank=True) sky_birth_lat = models.DecimalField(max_digits=9, decimal_places=4, null=True, blank=True)
sky_birth_lon = models.DecimalField(max_digits=9, decimal_places=4, null=True, blank=True) sky_birth_lon = models.DecimalField(max_digits=9, decimal_places=4, null=True, blank=True)
sky_birth_place = models.CharField(max_length=255, blank=True) sky_birth_place = models.CharField(max_length=255, blank=True)
sky_birth_tz = models.CharField(max_length=64, blank=True)
sky_house_system = models.CharField(max_length=1, blank=True, default="O") sky_house_system = models.CharField(max_length=1, blank=True, default="O")
sky_chart_data = models.JSONField(null=True, blank=True) sky_chart_data = models.JSONField(null=True, blank=True)

View File

@@ -19,12 +19,14 @@
<div class="natus-field"> <div class="natus-field">
<label for="id_nf_date">Birth date</label> <label for="id_nf_date">Birth date</label>
<input id="id_nf_date" name="date" type="date" required> <input id="id_nf_date" name="date" type="date" required
{% if saved_birth_dt %}value="{{ saved_birth_dt|date:'Y-m-d' }}"{% endif %}>
</div> </div>
<div class="natus-field"> <div class="natus-field">
<label for="id_nf_time">Birth time</label> <label for="id_nf_time">Birth time</label>
<input id="id_nf_time" name="time" type="time" value="12:00"> <input id="id_nf_time" name="time" type="time"
value="{% if saved_birth_dt %}{{ saved_birth_dt|time:'H:i' }}{% else %}12:00{% endif %}">
<small>Local time at birth place. Use 12:00 if unknown.</small> <small>Local time at birth place. Use 12:00 if unknown.</small>
</div> </div>
@@ -33,7 +35,8 @@
<div class="natus-place-wrap"> <div class="natus-place-wrap">
<input id="id_nf_place" name="place" type="text" <input id="id_nf_place" name="place" type="text"
placeholder="Start typing a city…" placeholder="Start typing a city…"
autocomplete="off"> autocomplete="off"
{% if saved_birth_place %}value="{{ saved_birth_place }}"{% endif %}>
<button type="button" id="id_nf_geolocate" <button type="button" id="id_nf_geolocate"
class="btn btn-secondary btn-sm" class="btn btn-secondary btn-sm"
title="Use device location"> title="Use device location">
@@ -47,19 +50,22 @@
<div> <div>
<label>Latitude</label> <label>Latitude</label>
<input id="id_nf_lat" name="lat" type="text" <input id="id_nf_lat" name="lat" type="text"
placeholder="—" readonly tabindex="-1"> placeholder="—" readonly tabindex="-1"
{% if saved_birth_lat %}value="{{ saved_birth_lat }}"{% endif %}>
</div> </div>
<div> <div>
<label>Longitude</label> <label>Longitude</label>
<input id="id_nf_lon" name="lon" type="text" <input id="id_nf_lon" name="lon" type="text"
placeholder="—" readonly tabindex="-1"> placeholder="—" readonly tabindex="-1"
{% if saved_birth_lon %}value="{{ saved_birth_lon }}"{% endif %}>
</div> </div>
</div> </div>
<div class="natus-field"> <div class="natus-field">
<label for="id_nf_tz">Timezone</label> <label for="id_nf_tz">Timezone</label>
<input id="id_nf_tz" name="tz" type="text" <input id="id_nf_tz" name="tz" type="text"
placeholder="auto-detected from location"> placeholder="auto-detected from location"
{% if saved_birth_tz %}value="{{ saved_birth_tz }}"{% endif %}>
<small id="id_nf_tz_hint"></small> <small id="id_nf_tz_hint"></small>
</div> </div>
@@ -109,9 +115,6 @@
const NOMINATIM = 'https://nominatim.openstreetmap.org/search'; const NOMINATIM = 'https://nominatim.openstreetmap.org/search';
const USER_AGENT = 'EarthmanRPG/1.0 (https://earthmanrpg.me)'; const USER_AGENT = 'EarthmanRPG/1.0 (https://earthmanrpg.me)';
// localStorage key — fixed for the user's personal sky (not room-scoped)
const LS_KEY = 'natus-form:dashboard:sky';
let _lastChartData = null; let _lastChartData = null;
let _placeDebounce = null; let _placeDebounce = null;
let _chartDebounce = null; let _chartDebounce = null;
@@ -120,34 +123,6 @@
NatusWheel.preload(); NatusWheel.preload();
// ── localStorage persistence ────────────────────────────────────────────
function _saveForm() {
const data = {
date: document.getElementById('id_nf_date').value,
time: document.getElementById('id_nf_time').value,
place: placeInput.value,
lat: latInput.value,
lon: lonInput.value,
tz: tzInput.value,
};
try { localStorage.setItem(LS_KEY, JSON.stringify(data)); } catch (_) {}
}
function _restoreForm() {
let data;
try { data = JSON.parse(localStorage.getItem(LS_KEY) || 'null'); } catch (_) {}
if (!data) return;
if (data.date) document.getElementById('id_nf_date').value = data.date;
if (data.time) document.getElementById('id_nf_time').value = data.time;
if (data.place) placeInput.value = data.place;
if (data.lat) latInput.value = data.lat;
if (data.lon) lonInput.value = data.lon;
if (data.tz) { tzInput.value = data.tz; tzHint.textContent = 'Auto-detected from coordinates.'; }
// If we have enough data from localStorage, kick off a wheel draw
if (_formReady()) schedulePreview();
}
// ── Status helper ─────────────────────────────────────────────────────── // ── Status helper ───────────────────────────────────────────────────────
function setStatus(msg, type) { function setStatus(msg, type) {
@@ -209,7 +184,6 @@
latInput.value = parseFloat(place.lat).toFixed(4); latInput.value = parseFloat(place.lat).toFixed(4);
lonInput.value = parseFloat(place.lon).toFixed(4); lonInput.value = parseFloat(place.lon).toFixed(4);
hideSuggestions(); hideSuggestions();
_saveForm();
schedulePreview(); schedulePreview();
} }
@@ -230,8 +204,7 @@
}) })
.then(r => r.json()) .then(r => r.json())
.then(data => { placeInput.value = _cityName(data.address) || data.display_name || ''; }) .then(data => { placeInput.value = _cityName(data.address) || data.display_name || ''; })
.catch(() => {}) .catch(() => {});
.finally(() => _saveForm());
setStatus(''); setStatus('');
schedulePreview(); schedulePreview();
}, },
@@ -251,7 +224,6 @@
form.addEventListener('input', (e) => { form.addEventListener('input', (e) => {
if (e.target === placeInput) return; if (e.target === placeInput) return;
_saveForm();
clearTimeout(_chartDebounce); clearTimeout(_chartDebounce);
_chartDebounce = setTimeout(schedulePreview, CHART_DELAY); _chartDebounce = setTimeout(schedulePreview, CHART_DELAY);
}); });
@@ -312,6 +284,7 @@
birth_lat: parseFloat(latInput.value), birth_lat: parseFloat(latInput.value),
birth_lon: parseFloat(lonInput.value), birth_lon: parseFloat(lonInput.value),
birth_place: placeInput.value, birth_place: placeInput.value,
birth_tz: tzInput.value.trim(),
house_system: _lastChartData.house_system || 'O', house_system: _lastChartData.house_system || 'O',
chart_data: _lastChartData, chart_data: _lastChartData,
}; };
@@ -340,9 +313,9 @@
return m ? m[1] : ''; return m ? m[1] : '';
} }
// ── Restore persisted form on load ─────────────────────────────────────── // ── Auto-preview on load if form is pre-filled from saved sky ───────────
_restoreForm(); if (_formReady()) schedulePreview();
})(); })();
</script> </script>
{% endblock %} {% endblock %}