diff --git a/djangorota/rota/models.py b/djangorota/rota/models.py index e3bac1f..e738f37 100644 --- a/djangorota/rota/models.py +++ b/djangorota/rota/models.py @@ -171,6 +171,83 @@ class Worker(models.Model): if isinstance(opts, dict): # shallow merge; keys in `options` override defaults above when present pyd_kwargs.update(opts) + + # Defensive normalization: the DB may contain `null`/`[]` for fields + # that the pydantic model expects to be ints/dicts/lists. Coerce a + # few common keys to safe defaults so Worker(**kwargs) doesn't raise + # on older or malformed records. + int_keys = [ + "locum_max_shifts", + "locum_max_shifts_per_week", + "bank_holiday_extra", + "prefer_multi_shift_together", + "weekend_shift_target_number", + ] + dict_keys = [ + "previous_shifts", + "shift_balance_extra", + "force_assign_with", + "max_shifts_per_week_by_shift_name", + "assign_as_block_preferences", + "shift_fte_overrides", + ] + list_keys = [ + "oop", + "not_available_to_work", + "pref_not_to_work", + "work_requests", + "locum_availability", + "allowed_multi_shift_sets", + "hard_day_dependencies", + "hard_day_exclusions", + "forced_assignments", + "forced_assignments_by_date", + ] + + for k in int_keys: + v = pyd_kwargs.get(k, None) + try: + if v is None: + pyd_kwargs[k] = 0 + else: + pyd_kwargs[k] = int(v) + except Exception: + pyd_kwargs[k] = 0 + + for k in dict_keys: + v = pyd_kwargs.get(k, None) + if not isinstance(v, dict): + # If a list was stored accidentally (common migration/serialization + # mistakes) coerce to an empty dict rather than letting pydantic + # fail. + pyd_kwargs[k] = {} + + for k in list_keys: + v = pyd_kwargs.get(k, None) + if v is None: + pyd_kwargs[k] = [] + elif isinstance(v, list): + # leave as-is + pass + elif isinstance(v, (set, tuple)): + pyd_kwargs[k] = list(v) + else: + pyd_kwargs[k] = [] + + # allowed_multi_shift_sets is expected to be a list of sets; if the + # DB contains lists-of-lists convert inner lists to sets. + ams = pyd_kwargs.get("allowed_multi_shift_sets") + if isinstance(ams, list): + new = [] + for item in ams: + if isinstance(item, list): + new.append(set(item)) + elif isinstance(item, (set, tuple)): + new.append(set(item)) + else: + # leave unknown types to pydantic to either accept or coerce + new.append(item) + pyd_kwargs["allowed_multi_shift_sets"] = new except Exception: # ignore any issues reading options and proceed with core fields pass