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", {
|
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})
|
||||||
|
|
||||||
|
|||||||
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_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)
|
||||||
|
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
Reference in New Issue
Block a user