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:
@@ -303,9 +303,12 @@ def sky_view(request):
|
||||
return render(request, "apps/dashboard/sky.html", {
|
||||
"preview_url": request.build_absolute_uri("/dashboard/sky/preview"),
|
||||
"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_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",
|
||||
})
|
||||
|
||||
@@ -338,12 +341,13 @@ def sky_save(request):
|
||||
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_birth_tz = body.get('birth_tz', '')
|
||||
user.sky_house_system = body.get('house_system', 'O')
|
||||
user.sky_chart_data = body.get('chart_data')
|
||||
|
||||
user.save(update_fields=[
|
||||
'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})
|
||||
|
||||
|
||||
18
src/apps/lyric/migrations/0019_sky_birth_tz.py
Normal file
18
src/apps/lyric/migrations/0019_sky_birth_tz.py
Normal 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),
|
||||
),
|
||||
]
|
||||
@@ -52,6 +52,7 @@ class User(AbstractBaseUser):
|
||||
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_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_chart_data = models.JSONField(null=True, blank=True)
|
||||
|
||||
|
||||
@@ -19,12 +19,14 @@
|
||||
|
||||
<div class="natus-field">
|
||||
<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 class="natus-field">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@@ -33,7 +35,8 @@
|
||||
<div class="natus-place-wrap">
|
||||
<input id="id_nf_place" name="place" type="text"
|
||||
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"
|
||||
class="btn btn-secondary btn-sm"
|
||||
title="Use device location">
|
||||
@@ -47,19 +50,22 @@
|
||||
<div>
|
||||
<label>Latitude</label>
|
||||
<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>
|
||||
<label>Longitude</label>
|
||||
<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 class="natus-field">
|
||||
<label for="id_nf_tz">Timezone</label>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@@ -109,9 +115,6 @@
|
||||
const NOMINATIM = 'https://nominatim.openstreetmap.org/search';
|
||||
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 _placeDebounce = null;
|
||||
let _chartDebounce = null;
|
||||
@@ -120,34 +123,6 @@
|
||||
|
||||
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 ───────────────────────────────────────────────────────
|
||||
|
||||
function setStatus(msg, type) {
|
||||
@@ -209,7 +184,6 @@
|
||||
latInput.value = parseFloat(place.lat).toFixed(4);
|
||||
lonInput.value = parseFloat(place.lon).toFixed(4);
|
||||
hideSuggestions();
|
||||
_saveForm();
|
||||
schedulePreview();
|
||||
}
|
||||
|
||||
@@ -230,8 +204,7 @@
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => { placeInput.value = _cityName(data.address) || data.display_name || ''; })
|
||||
.catch(() => {})
|
||||
.finally(() => _saveForm());
|
||||
.catch(() => {});
|
||||
setStatus('');
|
||||
schedulePreview();
|
||||
},
|
||||
@@ -251,7 +224,6 @@
|
||||
|
||||
form.addEventListener('input', (e) => {
|
||||
if (e.target === placeInput) return;
|
||||
_saveForm();
|
||||
clearTimeout(_chartDebounce);
|
||||
_chartDebounce = setTimeout(schedulePreview, CHART_DELAY);
|
||||
});
|
||||
@@ -312,6 +284,7 @@
|
||||
birth_lat: parseFloat(latInput.value),
|
||||
birth_lon: parseFloat(lonInput.value),
|
||||
birth_place: placeInput.value,
|
||||
birth_tz: tzInput.value.trim(),
|
||||
house_system: _lastChartData.house_system || 'O',
|
||||
chart_data: _lastChartData,
|
||||
};
|
||||
@@ -340,9 +313,9 @@
|
||||
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>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user