feat(cells) Make organization avatar URL cell compatible#115689
Conversation
I was reviewing the URL exception list and saw that this URL needed an org scoped endpoint added. I've added a new URL path for that includes the organization's slug, and moved the URL generation into model methods so that the `absolute_url()` interface from `BaseAvatar` is provided by both the cell + control models. Refs INFRENG-319
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 692efbc. Configure here.
We have a bunch of middleware that does path checks and that logic runs before django has done URL route matching. The previous URL of `/organizations/:slug/avatar` is incompatible with the static prefix checks done for cookies, so I chose to go back to `/organization-avatar` as the prefix.
There was a problem hiding this comment.
nitpick: I think we can remove this attribute, looks like OrganizationAvatar.absolute_url uses reverse(), and never reads this attribute, and likewise AvatarBase.absolute_url does read this but it gets overridden here
| locality_url, f"/{OrganizationAvatar.url_path}/{avatar_replica.avatar_ident}/" | ||
| ), | ||
| "avatarUrl": avatar_replica.absolute_url(), | ||
| } |
There was a problem hiding this comment.
OrganizationAvatarReplica.absolute_url() raises DoesNotExist if OrganizationMapping is missing
Calling avatar_replica.absolute_url() in the serializer will propagate an unhandled OrganizationMapping.DoesNotExist if the mapping row hasn't been replicated yet or was deleted, crashing the endpoint — wrap the call in a try/except or fall back to None for avatarUrl.
Evidence
avatar_replica.absolute_url()(inorganizationavatarreplica.py, line 33) callsOrganizationMapping.objects.get_from_cache(organization_id=self.organization_id).get_from_cacheultimately calls.get(**kwargs)on a cache miss (confirmed indb/models/manager/base.pylines 314, 323).- If the
OrganizationMappingrow does not exist, Django raisesOrganizationMapping.DoesNotExist. - Neither
absolute_url()nor the serializer'sserialize()method catches this exception. - An
OrganizationAvatarReplicacan exist before its correspondingOrganizationMappinghas been fully replicated across silos, making this a real race condition.
Identified by Warden sentry-backend-bugs · 7UC-E35
There was a problem hiding this comment.
If the mapping record is gone the org is too.
| def absolute_url(self) -> str: | ||
| """ | ||
| Provide a consistent interface with OrganizationAvatar to simplify serializers. | ||
| """ |
There was a problem hiding this comment.
OrganizationMapping.objects.get_from_cache() raises unhandled DoesNotExist in absolute_url
If the OrganizationMapping for this avatar's organization_id has been deleted or not yet created, get_from_cache raises OrganizationMapping.DoesNotExist, which propagates unhandled through the serializer and causes a 500.
Evidence
get_from_cacheinBaseManager(base.py:283) is a thin caching wrapper aroundself.get(**kwargs)— it does not catchDoesNotExist.absolute_urlcallsOrganizationMapping.objects.get_from_cache(organization_id=self.organization_id)with no try/except.- The serializer (organization.py:387) calls
avatar_replica.absolute_url()unconditionally when anOrganizationAvatarReplicaexists. - An org can have a replica row while its mapping is concurrently deleted or not yet replicated, triggering the exception during serialization.
Identified by Warden sentry-backend-bugs · HXD-U5J
| @@ -58,3 +60,9 @@ def handle_async_deletion( | |||
| control_replica_service.delete_organization_avatar_replica( | |||
| organization_id=shard_identifier, | |||
| ) | |||
There was a problem hiding this comment.
Organization.objects.get_from_cache() in absolute_url() raises DoesNotExist if organization is deleted
If the organization has been deleted while its avatar still exists, Organization.objects.get_from_cache(id=self.organization_id) will raise Organization.DoesNotExist, crashing any serialization that calls absolute_url() (e.g. the organization serializer at lines 387 and 545).
Evidence
absolute_url()callsOrganization.objects.get_from_cache(id=self.organization_id)with no try/except.get_from_cacheultimately calls.get(**kwargs)(base.py line 344) which raisesDoesNotExistwhen the record is absent.- The serializer (
organization.pylines 387, 545) callsavatar_replica.absolute_url()/attrs["avatar"].absolute_url()without guarding against this exception. OrganizationAvataris aReplicatedCellModel, so the avatar replica can outlive the organization if deletion is not perfectly ordered.
Identified by Warden sentry-backend-bugs · ENG-WHM
There was a problem hiding this comment.
If the org is gone so is the avatar.
I was reviewing the URL exception list and saw that this URL needed an org scoped endpoint added. I've added a new URL path for that includes the organization's slug, and moved the URL generation into model methods so that the `absolute_url()` interface from `BaseAvatar` is provided by both the cell + control models. Refs INFRENG-319

I was reviewing the URL exception list and saw that this URL needed an org scoped endpoint added. I've added a new URL path for that includes the organization's slug, and moved the URL generation into model methods so that the
absolute_url()interface fromBaseAvataris provided by both the cell + control models.Refs INFRENG-319