Add worker import functionality with CSV upload and text input options
This commit is contained in:
@@ -376,6 +376,116 @@ def worker_create(request):
|
||||
return render(request, "rota/worker_form.html", {"form": form})
|
||||
|
||||
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def worker_import(request, rota_id=None):
|
||||
"""HTMX endpoint: import workers from CSV or pasted text.
|
||||
|
||||
Accepts a file upload named 'file' (CSV) or a textarea 'workers_text'.
|
||||
CSV columns supported: name,email,site,grade,fte (headers optional).
|
||||
When `rota_id` is provided, the created workers will be assigned to
|
||||
that rota.
|
||||
"""
|
||||
is_hx = request.headers.get("HX-Request", "false").lower() == "true"
|
||||
|
||||
rota = None
|
||||
if rota_id is not None:
|
||||
try:
|
||||
rota = get_object_or_404(RotaSchedule, pk=rota_id)
|
||||
except Exception:
|
||||
rota = None
|
||||
|
||||
if request.method == "GET":
|
||||
# render modal partial
|
||||
return render(request, "rota/partials/worker_import_modal.html", {"rota": rota})
|
||||
|
||||
# POST: parse uploaded file or textarea
|
||||
created = []
|
||||
errors = []
|
||||
assign_to_rota = request.POST.get("assign_to_rota") == "1"
|
||||
|
||||
import csv
|
||||
try:
|
||||
upload = request.FILES.get("file")
|
||||
if upload:
|
||||
# decode bytes to text
|
||||
try:
|
||||
text = upload.read().decode("utf-8")
|
||||
except Exception:
|
||||
text = upload.read().decode("latin-1")
|
||||
else:
|
||||
text = request.POST.get("workers_text", "")
|
||||
|
||||
# Normalize lines: if simple newline-separated names/emails, make a CSV
|
||||
reader = csv.DictReader(text.splitlines())
|
||||
rows = list(reader)
|
||||
if not rows:
|
||||
# Try parsing as simple CSV without headers
|
||||
reader2 = csv.reader([line for line in text.splitlines() if line.strip()])
|
||||
for r in reader2:
|
||||
# Map positional columns: name,email,site,grade,fte
|
||||
while len(r) < 5:
|
||||
r.append("")
|
||||
rows.append({"name": r[0].strip(), "email": r[1].strip(), "site": r[2].strip(), "grade": r[3].strip(), "fte": r[4].strip()})
|
||||
|
||||
from .models import Assignment
|
||||
|
||||
for i, row in enumerate(rows):
|
||||
try:
|
||||
# normalize keys lower-case
|
||||
if not isinstance(row, dict):
|
||||
continue
|
||||
r = {k.strip().lower(): (v if v is not None else "") for k, v in row.items()}
|
||||
name = r.get("name") or r.get("full_name") or r.get("username")
|
||||
if not name or str(name).strip() == "":
|
||||
errors.append(f"Row {i+1}: missing name")
|
||||
continue
|
||||
email = r.get("email") or ""
|
||||
site = r.get("site") or ""
|
||||
try:
|
||||
grade = int(r.get("grade")) if r.get("grade") not in (None, "") else None
|
||||
except Exception:
|
||||
grade = None
|
||||
try:
|
||||
fte_raw = r.get("fte")
|
||||
if fte_raw in (None, ""):
|
||||
fte = 1.0
|
||||
else:
|
||||
fte = float(fte_raw)
|
||||
except Exception:
|
||||
fte = 1.0
|
||||
|
||||
w = Worker.objects.create(name=name.strip(), email=email.strip(), site=site.strip(), grade=grade, fte=fte)
|
||||
created.append(w)
|
||||
if assign_to_rota and rota is not None:
|
||||
try:
|
||||
Assignment.objects.create(worker=w, rota=rota)
|
||||
except Exception:
|
||||
try:
|
||||
rota.workers.add(w)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
errors.append(f"Row {i+1}: {exc}")
|
||||
except Exception as exc:
|
||||
errors.append(str(exc))
|
||||
|
||||
# If HTMX, return updated worker list partial and close modal
|
||||
if is_hx and rota is not None:
|
||||
worker_list_html = render_to_string(
|
||||
"rota/partials/worker_list.html",
|
||||
{"rota": rota},
|
||||
request=request,
|
||||
)
|
||||
# Return both new worker list and a trigger to close modal
|
||||
resp = f'<div id="worker-list" hx-swap-oob="true">{worker_list_html}</div>'
|
||||
response = HttpResponse(resp)
|
||||
response["HX-Trigger"] = "closeModal"
|
||||
return response
|
||||
|
||||
# Non-HTMX: redirect back to rota detail with message (flash messages not added here)
|
||||
return redirect("rota:rota_detail", rota_id=rota.id if rota is not None else None)
|
||||
|
||||
|
||||
def worker_detail(request, worker_id):
|
||||
worker = get_object_or_404(Worker, pk=worker_id)
|
||||
if request.method == "POST":
|
||||
|
||||
Reference in New Issue
Block a user