diff --git a/djangorota/djangorota/templates/rota/leave_request.html b/djangorota/djangorota/templates/rota/leave_request.html
new file mode 100644
index 0000000..38f8116
--- /dev/null
+++ b/djangorota/djangorota/templates/rota/leave_request.html
@@ -0,0 +1,16 @@
+{% extends 'base.html' %}
+{% load crispy_forms_tags %}
+{% block content %}
+
+
Request leave{% if worker %} for {{ worker.name }}{% endif %}
+
+
+{% endblock %}
diff --git a/djangorota/djangorota/templates/rota/partials/leave_request_modal.html b/djangorota/djangorota/templates/rota/partials/leave_request_modal.html
new file mode 100644
index 0000000..0f7bd10
--- /dev/null
+++ b/djangorota/djangorota/templates/rota/partials/leave_request_modal.html
@@ -0,0 +1,25 @@
+{% load crispy_forms_tags %}
+
diff --git a/djangorota/djangorota/templates/rota/partials/worker_form_modal.html b/djangorota/djangorota/templates/rota/partials/worker_form_modal.html
index c555f4e..8c1603b 100644
--- a/djangorota/djangorota/templates/rota/partials/worker_form_modal.html
+++ b/djangorota/djangorota/templates/rota/partials/worker_form_modal.html
@@ -124,6 +124,140 @@
// initial render
try{ render(); }catch(e){ console && console.error && console.error(e); }
+ })();
+
+
+
+
Other structured fields
+
Quick editors for common lists so you don't need to edit JSON manually.
+
+
+
+
Out of programme
+
+
+
+
+
+
Not available to work
+
+
+
+
+
+
Preferences not to work
+
+
+
+
+
+
Work requests
+
+
+
+
+
+
Locum availability
+
+
+
+
+
+
Forced assignments (week,day,shift)
+
+
+
+
+
+
Forced assignments by date
+
+
+
+
+
+
Hard day dependencies
+
+
+
+
+
+
Hard day exclusions
+
+
+
+
+
+
Allowed multi-shift sets
+
+
+
+
+
+
+
diff --git a/djangorota/rota/urls.py b/djangorota/rota/urls.py
index 45507d7..e87783c 100644
--- a/djangorota/rota/urls.py
+++ b/djangorota/rota/urls.py
@@ -16,6 +16,7 @@ urlpatterns = [
path("worker/
//edit/", views.worker_edit, name="worker_edit"),
path("worker///delete/", views.worker_delete, name="worker_delete"),
path("worker//", views.worker_detail, name="worker_detail"),
+ path("leave/request/", views.request_leave, name="request_leave"),
path("run//", views.rota_run_detail, name="rota_run_detail"),
path("run//export/html/", views.rota_run_export_html, name="rota_run_export_html"),
path("run//export/download/", views.rota_run_export_download, name="rota_run_export_download"),
diff --git a/djangorota/rota/views.py b/djangorota/rota/views.py
index 9bb6b6f..4aa9807 100644
--- a/djangorota/rota/views.py
+++ b/djangorota/rota/views.py
@@ -420,6 +420,68 @@ def rota_options_update(request, rota_id):
return redirect("rota:rota_detail", rota_id=rota.id)
+@require_http_methods(["GET", "POST"])
+def request_leave(request):
+ """Allow a worker to request leave for themselves.
+
+ Resolution strategy:
+ - If the user is authenticated, try to find a `Worker` with matching
+ email (first match). If found, use that worker.
+ - Otherwise, fall back to `worker_id` provided via GET/POST.
+
+ Supports HTMX modal flow when `HX-Request` header is present.
+ """
+ is_hx = request.headers.get("HX-Request", "false").lower() == "true"
+
+ worker = None
+ # try authenticated user -> worker by email
+ try:
+ if request.user and request.user.is_authenticated and getattr(request.user, "email", None):
+ worker = Worker.objects.filter(email=request.user.email).first()
+ except Exception:
+ worker = None
+
+ # fallback to explicit worker_id
+ if worker is None:
+ worker_id = request.GET.get("worker_id") or request.POST.get("worker_id")
+ if worker_id:
+ try:
+ worker = get_object_or_404(Worker, pk=int(worker_id))
+ except Exception:
+ worker = None
+
+ if request.method == "POST":
+ form = LeaveForm(request.POST)
+ if form.is_valid():
+ leave = form.save(commit=False)
+ if worker is None:
+ return HttpResponseBadRequest("Worker not specified or not found")
+ leave.worker = worker
+ leave.save()
+ # On HTMX modal post, return an OOB swap to close modal and optionally
+ # update worker detail partial if present.
+ if is_hx:
+ resp = ''
+ # optionally include trigger
+ response = HttpResponse(resp)
+ response["HX-Trigger"] = "closeModal"
+ return response
+ return redirect("rota:worker_detail", worker_id=worker.id)
+ else:
+ form = LeaveForm()
+
+ # Render modal or full page
+ if is_hx:
+ modal_html = render_to_string(
+ "rota/partials/leave_request_modal.html",
+ {"form": form, "form_action": request.get_full_path(), "worker": worker},
+ request=request,
+ )
+ return HttpResponse(modal_html)
+
+ return render(request, "rota/leave_request.html", {"form": form, "worker": worker})
+
+
def rota_options(request, rota_id):
"""HTMX-aware modal edit for rota.options JSON.