diff --git a/djangorota/djangorota/templates/rota/partials/shift_form_modal.html b/djangorota/djangorota/templates/rota/partials/shift_form_modal.html index be99077..e71aaaf 100644 --- a/djangorota/djangorota/templates/rota/partials/shift_form_modal.html +++ b/djangorota/djangorota/templates/rota/partials/shift_form_modal.html @@ -16,6 +16,170 @@ function _close_shift_modal_targets(){
{% csrf_token %} {{ form|crispy }} + + +
+

Shift constraints

+

Add constraint entries. The form stores a JSON list; this editor helps build that list.

+
+
+ +
+
+ +
diff --git a/djangorota/rota/forms.py b/djangorota/rota/forms.py index a7e3470..8d9b035 100644 --- a/djangorota/rota/forms.py +++ b/djangorota/rota/forms.py @@ -334,9 +334,32 @@ class ShiftForm(forms.Form): help_text="Maximum allowed difference in allocated shifts for this shift (leave blank for default)", ) + # Allow entering shift-specific constraints as JSON. This should be a + # list of constraint dicts compatible with the scheduler's SingleShift + # `constraints` field. Example: `[{"name": "night", "options": {...}}]` + constraints = forms.CharField( + required=False, + widget=forms.Textarea(attrs={"rows": 4, "class": "textarea"}), + help_text="Optional JSON list of constraint objects for this shift (e.g. [{'name':'night','options':{}}])", + ) + def clean_sites(self): val = self.cleaned_data["sites"] sites = [s.strip() for s in val.split(",") if s.strip()] if not sites: raise forms.ValidationError("At least one site is required") return sites + + def clean_constraints(self): + val = self.cleaned_data.get("constraints") + if not val: + return [] + try: + parsed = json.loads(val) + if not isinstance(parsed, list): + raise forms.ValidationError("Constraints must be a JSON list of constraint objects") + return parsed + except forms.ValidationError: + raise + except Exception as exc: + raise forms.ValidationError(f"Invalid JSON for constraints: {exc}") diff --git a/djangorota/rota/views.py b/djangorota/rota/views.py index 7e89360..9bb6b6f 100644 --- a/djangorota/rota/views.py +++ b/djangorota/rota/views.py @@ -882,6 +882,7 @@ def shift_add(request, rota_id): "workers_required": int(data["workers_required"]), "assign_as_block": bool(data["assign_as_block"]), "balance_offset": data.get("balance_offset"), + "constraints": data.get("constraints", []), } current = rota.shifts or [] @@ -930,6 +931,7 @@ def shift_edit(request, rota_id, idx): "days": data["days"], "workers_required": int(data["workers_required"]), "assign_as_block": bool(data["assign_as_block"]), + "constraints": data.get("constraints", []), } current = rota.shifts or [] current[idx] = shift_dict @@ -958,6 +960,7 @@ def shift_edit(request, rota_id, idx): "workers_required": shift.get("workers_required"), "assign_as_block": shift.get("assign_as_block", False), "balance_offset": shift.get("balance_offset", None), + "constraints": json.dumps(shift.get("constraints", [])) if shift.get("constraints") is not None else "", } form = ShiftForm(initial=initial) modal_html = render_to_string("rota/partials/shift_form_modal.html", {"form": form, "rota": rota, "form_action": request.path}, request=request)