| 112 | return user |
| 113 | |
| 114 | def update(self, instance, validated_data): |
| 115 | password = validated_data.pop("password", None) |
| 116 | channel_profiles = validated_data.pop("channel_profiles", None) |
| 117 | |
| 118 | # Merge custom_properties instead of replacing (prevents data loss) |
| 119 | # null values are explicit deletions; all other values overwrite existing |
| 120 | custom_properties = validated_data.pop("custom_properties", None) |
| 121 | if custom_properties is not None: |
| 122 | existing = instance.custom_properties or {} |
| 123 | merged = dict(existing) |
| 124 | for k, v in custom_properties.items(): |
| 125 | if v is None: |
| 126 | merged.pop(k, None) |
| 127 | else: |
| 128 | merged[k] = v |
| 129 | # Scrub stale nav IDs so the DB self-heals on next save |
| 130 | for nav_field in ('navOrder', 'hiddenNav'): |
| 131 | if nav_field in merged and isinstance(merged[nav_field], list): |
| 132 | merged[nav_field] = [ |
| 133 | item for item in merged[nav_field] |
| 134 | if item in VALID_NAV_ITEM_IDS |
| 135 | ] |
| 136 | instance.custom_properties = merged |
| 137 | |
| 138 | for attr, value in validated_data.items(): |
| 139 | setattr(instance, attr, value) |
| 140 | |
| 141 | if password: |
| 142 | instance.set_password(password) |
| 143 | |
| 144 | instance.save() |
| 145 | |
| 146 | if channel_profiles is not None: |
| 147 | instance.channel_profiles.set(channel_profiles) |
| 148 | |
| 149 | return instance |