Added WIP Backup System

This commit is contained in:
st01765
2026-02-04 13:32:37 +11:00
parent 74d6f48136
commit 3d23a13f99
7 changed files with 499 additions and 10 deletions
+279 -3
View File
@@ -1,8 +1,6 @@
import datetime
from django.forms import model_to_dict
from coord.models import *
from ..models import *
def _to_date(date):
@@ -10,6 +8,284 @@ def _to_date(date):
return None
return date.strftime("%Y-%m-%d")
def serialize_model(instance, ignore_fields=None, extra_mappings=None):
if ignore_fields is None:
ignore_fields = []
if extra_mappings is None:
extra_mappings = {}
data = model_to_dict(instance)
for field in ignore_fields:
data.pop(field, None)
for field, value in extra_mappings.items():
data[field] = value
return data
def serialize_suburb(suburb):
return serialize_model(
suburb
)
def serialize_school(school):
return serialize_model(
school,
extra_mappings={
"suburb": school.suburb.name if school.suburb else None,
}
)
def serialize_bus(bus):
bus_dict = serialize_model(
bus,
ignore_fields=["company"],
)
bus_dict["drivers"] = [
serialize_model(driver, ignore_fields=["bus"])
for driver in Driver.objects.filter(bus=bus)
]
bus_dict["shuttles"] = [
serialize_model(shuttle, ignore_fields=["bus"])
for shuttle in Shuttle.objects.filter(bus=bus)
]
bus_dict["bus_stops"] = [
serialize_model(
bus_stop,
ignore_fields=["bus"],
extra_mappings={
"am_time": bus_stop.am_time.strftime("%H:%M:%S") if bus_stop.am_time else None,
"pm_time": bus_stop.pm_time.strftime("%H:%M:%S") if bus_stop.pm_time else None,
},
)
for bus_stop in BusStop.objects.filter(bus=bus)
]
return bus_dict
def serialize_company(company):
company_dict = serialize_model(
company,
extra_mappings={
"suburb": company.suburb.name if company.suburb else None,
},
)
company_dict["buses"] = [serialize_bus(bus) for bus in Bus.objects.filter(company=company)]
return company_dict
def serialize_traveller(traveller):
traveller_dict = serialize_model(
traveller,
ignore_fields=['travel_start_date', 'travel_end_date', 'is_active', 'is_archived', 'bus_stops', 'fee_per_term', 'address'],
extra_mappings = {
"school": traveller.school.name if traveller.school else None,
"dob": _to_date(traveller.dob),
"assessment_date": _to_date(traveller.assessment_date),
"created_on": traveller.created_on.strftime("%Y-%m-%d %H:%M:%S %Z"),
"last_edit": traveller.last_edit.strftime("%Y-%m-%d %H:%M:%S %Z"),
}
)
traveller_dict["families"] = [
serialize_model(
family,
ignore_fields=["traveller"],
extra_mappings={
"residential_suburb": family.residential_suburb.name if family.residential_suburb else None,
"postal_suburb": family.postal_suburb.name if family.postal_suburb else None,
},
)
for family in Family.objects.filter(traveller=traveller)
]
traveller_dict["bus_stops"] = [
serialize_model(
route,
ignore_fields=["traveller"],
extra_mappings={
"travel_start_date": _to_date(traveller.travel_start_date),
}
)
for route in TravellerRoute.objects.filter(traveller=traveller)
]
return traveller_dict
def serialize_all():
return {
"suburbs": [serialize_suburb(suburb) for suburb in Suburb.objects.all()],
"schools": [serialize_school(school) for school in School.objects.all()],
"companies": [serialize_company(company) for company in Company.objects.all()],
"travellers": [serialize_traveller(traveller) for traveller in Traveller.objects.all()],
}
def compare_backup(backup_content):
import json
# Parse uploaded file
backup_data = json.loads(backup_content)
# Current DB state
current_data = serialize_all()
def compare_dicts_shallow(current, backup, ignore_keys=None):
"""Compare two dicts at a single level."""
ignore_keys = ignore_keys or []
modifications = []
keys = set(current.keys()) | set(backup.keys())
for key in keys:
if key in ignore_keys:
continue
cur_val = current.get(key)
bak_val = backup.get(key)
if cur_val != bak_val:
if cur_val is None:
mod_type = "New"
elif bak_val is None:
mod_type = "Removed"
else:
mod_type = "Updated"
modifications.append({
"type": mod_type,
"key": key,
"old_value": cur_val,
"new_value": bak_val
})
return modifications
def compare_backup_dicts(current, backup, key, nested_keys=None):
current_list = current.get(key, [])
backup_list = backup.get(key, [])
if not (current_list and backup_list):
return []
nested_keys = nested_keys or []
current_map = {item[key]: item for item in current_list}
backup_map = {item[key]: item for item in backup_list}
results = []
# Check for added items
for name in backup_map:
if name not in current_map:
results.append({
"name": name,
"type": "New",
"modifications": [],
"inline_modifications": []
})
# Check for removed items
for name in current_map:
if name not in backup_map:
results.append({
"name": name,
"type": "Removed",
"modifications": [],
"inline_modifications": []
})
# Check for updated items
for name in current_map:
if name in backup_map:
cur_item = current_map[name]
bak_item = backup_map[name]
modifications = compare_dicts_shallow(cur_item, bak_item, ignore_keys=nested_keys)
inline_modifications = []
for nested_key in nested_keys:
nested_diff = compare_backup_dicts(current_list, current_list, nested_key, nested_keys)
if nested_diff:
inline_modifications.append({
"name": nested_key.capitalize(),
"modifications": nested_diff
})
if modifications or inline_modifications:
results.append({
"name": name,
"type": "Updated",
"modifications": modifications,
"inline_modifications": inline_modifications
})
return results
diff_suburbs = compare_backup_dicts(
current_data,
backup_data,
"suburbs"
)
diff_schools = compare_backup_dicts(
current_data,
backup_data,
"schools"
)
diff_companies = compare_backup_dicts(
current_data,
backup_data,
"companies",
["buses", "drivers", "shuttles", "bus_stops"]
)
diff_travellers = compare_backup_dicts(
current_data,
backup_data,
"travellers",
["families", "bus_stops"]
)
# Helper function to compare lists by key
def compare_lists(current, backup, key="name"):
current_map = {item[key]: item for item in current}
backup_map = {item[key]: item for item in backup}
added = [backup_map[k] for k in backup_map if k not in current_map]
removed = [current_map[k] for k in current_map if k not in backup_map]
modified = [
(k, {"db": current_map[k], "backup": backup_map[k]})
for k in current_map
if k in backup_map and current_map[k] != backup_map[k]
]
return {
"add": len(added),
"remove": len(removed),
"modify": len(modified),
"details": {
"added": added,
"removed": removed,
"modified": modified,
},
}
results = {
"suburbs": compare_lists(current_data["suburbs"], backup_data["suburbs"], key="name"),
"schools": compare_lists(current_data["schools"], backup_data["schools"], key="name"),
"companies": compare_lists(current_data["companies"], backup_data["companies"], key="name"),
"travellers": compare_lists(current_data["travellers"], backup_data["travellers"], key="name"),
}
# Summary counts only
summary = {k: {m: v[m] for m in ["add", "remove", "modify"]} for k, v in results.items()}
return summary, results
def get_export_dict():
suburbs = []