HumanSignal/label-studio · error · MethodNotAllowed

Cannot update read-only field: {field}

Error message

Cannot update read-only field: {field}

What it means

UserAPI.partial_update in label_studio/users/api.py raises DRF MethodNotAllowed('PATCH') when the PATCH body includes any field listed in UserSerializerUpdate.Meta.read_only_fields. The super().partial_update() call has already run by this point, so the write succeeded but the request is then rejected with 405.

Source

Thrown at label_studio/users/api.py:191

    def create(self, request, *args, **kwargs):
        return super(UserAPI, self).create(request, *args, **kwargs)

    def perform_create(self, serializer):
        instance = serializer.save()
        self.request.user.active_organization.add_user(instance)

    def retrieve(self, request, *args, **kwargs):
        return super(UserAPI, self).retrieve(request, *args, **kwargs)

    def partial_update(self, request, *args, **kwargs):
        result = super(UserAPI, self).partial_update(request, *args, **kwargs)

        # throw MethodNotAllowed if read-only fields are attempted to be updated
        read_only_fields = self.get_serializer_class().Meta.read_only_fields
        for field in read_only_fields:
            if field in request.data:
                raise MethodNotAllowed('PATCH', detail=f'Cannot update read-only field: {field}')

        # newsletters
        if 'allow_newsletters' in request.data:
            user = User.objects.get(id=request.user.id)  # we need an updated user
            request.user.advanced_json = {  # request.user instance will be unchanged in request all the time
                'email': user.email,
                'allow_newsletters': user.allow_newsletters,
                'update-notifications': 1,
                'new-user': 0,
            }
        return result

    def destroy(self, request, *args, **kwargs):
        return super(UserAPI, self).destroy(request, *args, **kwargs)


@method_decorator(
    name='post',

View on GitHub (pinned to 0b49e9b539)

Solutions

  1. Remove read-only fields (e.g. 'id', 'username') from the PATCH body and send only mutable fields
  2. Build the payload explicitly instead of echoing the GET response
  3. Strip read-only fields client-side: payload = {k: v for k, v in user.items() if k not in read_only_fields}
  4. Check UserSerializerUpdate.Meta.read_only_fields for the exact blocked field names

Example fix

// before
client.patch(f"/api/users/{uid}", {"id": uid, "first_name": "Ann"})
// after
client.patch(f"/api/users/{uid}", {"first_name": "Ann"})
Defensive patterns

Strategy: validation

Validate before calling

READ_ONLY = {'id', 'username'}  # UserSerializerUpdate.Meta.read_only_fields
payload = {k: v for k, v in request_body.items() if k not in READ_ONLY}

Try / catch

try:
    client.update_user(payload)
except MethodNotAllowed as e:
    detail = getattr(e, 'detail', str(e))
    m = re.search(r'read-only field: (\w+)', str(detail))
    if m:
        payload.pop(m.group(1), None)
        client.update_user(payload)
    else:
        raise

Prevention

When it happens

Trigger: PATCH /api/current-user (or /api/users/<id>/) with a body containing read-only fields such as 'id', 'username', or other Meta.read_only_fields entries — e.g. {'id': 5, 'first_name': 'A'} or echoing back the full user object from a prior GET.

Common situations: Clients doing GET then PATCH with the full response body echoed back (including id/username); UI forms that bind all user fields and submit everything; SDK wrappers that merge the object with updates before sending.

Related errors


AI-assisted analysis of HumanSignal/label-studio@0b49e9b539 (2026-08-29). Data as JSON: /api/errors/f05dd3d58c1be3e8. Report an issue: GitHub.