From 7ce3c5725063025f1fda7510d9c8eec4af2e2187 Mon Sep 17 00:00:00 2001 From: Christinarlong Date: Wed, 14 Aug 2024 10:50:26 -0700 Subject: [PATCH 1/2] ref(control_silo): Move UserAvatar model to users module Part of moving control silo user related resources into the users module, this PR does not include an old UserRole import shim as it's not used in getsentry. Includes adding of types for functions. Apart of (#73856) --- src/sentry/api/endpoints/avatar/user.py | 2 +- src/sentry/api/serializers/models/user.py | 2 +- src/sentry/models/avatars/__init__.py | 2 -- src/sentry/notifications/utils/avatar.py | 2 +- src/sentry/testutils/factories.py | 2 +- src/sentry/users/models/user.py | 4 ++-- .../avatars => users/models}/user_avatar.py | 10 +++++----- src/sentry/users/services/user/serial.py | 2 +- src/sentry/web/frontend/user_avatar.py | 2 +- tests/sentry/api/endpoints/test_user_avatar.py | 2 +- tests/sentry/api/serializers/test_user.py | 2 +- .../sentry/models/test_organization_avatar.py | 17 +++++++++++++++++ tests/sentry/models/test_projectownership.py | 2 +- .../models/test_user_avatar.py} | 18 +----------------- tests/sentry/web/frontend/test_user_avatar.py | 2 +- 15 files changed, 35 insertions(+), 36 deletions(-) rename src/sentry/{models/avatars => users/models}/user_avatar.py (93%) create mode 100644 tests/sentry/models/test_organization_avatar.py rename tests/sentry/{models/test_avatar.py => users/models/test_user_avatar.py} (58%) diff --git a/src/sentry/api/endpoints/avatar/user.py b/src/sentry/api/endpoints/avatar/user.py index 6179cc0da08084..04d61788a61ef2 100644 --- a/src/sentry/api/endpoints/avatar/user.py +++ b/src/sentry/api/endpoints/avatar/user.py @@ -7,7 +7,7 @@ from sentry.api.base import control_silo_endpoint from sentry.api.bases.avatar import AvatarMixin from sentry.api.bases.user import UserEndpoint -from sentry.models.avatars.user_avatar import UserAvatar +from sentry.users.models.user_avatar import UserAvatar from sentry.users.services.user.serial import serialize_rpc_user from sentry.users.services.user.service import user_service diff --git a/src/sentry/api/serializers/models/user.py b/src/sentry/api/serializers/models/user.py index 31bdf393b40695..3a53ae966a965c 100644 --- a/src/sentry/api/serializers/models/user.py +++ b/src/sentry/api/serializers/models/user.py @@ -18,7 +18,6 @@ from sentry.auth.elevated_mode import has_elevated_mode from sentry.hybridcloud.services.organization_mapping import organization_mapping_service from sentry.models.authidentity import AuthIdentity -from sentry.models.avatars.user_avatar import UserAvatar from sentry.models.options.user_option import UserOption from sentry.models.organization import OrganizationStatus from sentry.models.organizationmapping import OrganizationMapping @@ -28,6 +27,7 @@ from sentry.organizations.services.organization import RpcOrganizationSummary from sentry.users.models.authenticator import Authenticator from sentry.users.models.user import User +from sentry.users.models.user_avatar import UserAvatar from sentry.users.models.userrole import UserRoleUser from sentry.users.services.user import RpcUser from sentry.utils.avatar import get_gravatar_url diff --git a/src/sentry/models/avatars/__init__.py b/src/sentry/models/avatars/__init__.py index 80a8e66fcc7a7d..0365753d58435f 100644 --- a/src/sentry/models/avatars/__init__.py +++ b/src/sentry/models/avatars/__init__.py @@ -2,12 +2,10 @@ from .control_base import ControlAvatarBase from .organization_avatar import OrganizationAvatar from .sentry_app_avatar import SentryAppAvatar -from .user_avatar import UserAvatar __all__ = ( "AvatarBase", "ControlAvatarBase", "OrganizationAvatar", "SentryAppAvatar", - "UserAvatar", ) diff --git a/src/sentry/notifications/utils/avatar.py b/src/sentry/notifications/utils/avatar.py index 0908bb377771be..6b2a3b80f9481e 100644 --- a/src/sentry/notifications/utils/avatar.py +++ b/src/sentry/notifications/utils/avatar.py @@ -4,8 +4,8 @@ from django.utils.html import format_html from django.utils.safestring import SafeString -from sentry.models.avatars.user_avatar import UserAvatar from sentry.users.models.user import User +from sentry.users.models.user_avatar import UserAvatar from sentry.users.services.user import RpcUser from sentry.utils.assets import get_asset_url from sentry.utils.avatar import get_email_avatar diff --git a/src/sentry/testutils/factories.py b/src/sentry/testutils/factories.py index 9dd8d9d5465a13..29a9053139a2eb 100644 --- a/src/sentry/testutils/factories.py +++ b/src/sentry/testutils/factories.py @@ -83,7 +83,6 @@ from sentry.models.authidentity import AuthIdentity from sentry.models.authprovider import AuthProvider from sentry.models.avatars.sentry_app_avatar import SentryAppAvatar -from sentry.models.avatars.user_avatar import UserAvatar from sentry.models.commit import Commit from sentry.models.commitauthor import CommitAuthor from sentry.models.commitfilechange import CommitFileChange @@ -167,6 +166,7 @@ UptimeSubscription, ) from sentry.users.models.user import User +from sentry.users.models.user_avatar import UserAvatar from sentry.users.models.userrole import UserRole from sentry.users.services.user import RpcUser from sentry.utils import loremipsum diff --git a/src/sentry/users/models/user.py b/src/sentry/users/models/user.py index ab6cd6e36c221e..375c5f84cd931e 100644 --- a/src/sentry/users/models/user.py +++ b/src/sentry/users/models/user.py @@ -41,7 +41,6 @@ from sentry.hybridcloud.outbox.category import OutboxCategory from sentry.integrations.types import EXTERNAL_PROVIDERS, ExternalProviders from sentry.locks import locks -from sentry.models.avatars import UserAvatar from sentry.models.lostpasswordhash import LostPasswordHash from sentry.models.organizationmapping import OrganizationMapping from sentry.models.organizationmembermapping import OrganizationMemberMapping @@ -50,6 +49,7 @@ from sentry.organizations.services.organization import RpcRegionUser, organization_service from sentry.types.region import find_all_region_names, find_regions_for_user from sentry.users.models.authenticator import Authenticator +from sentry.users.models.user_avatar import UserAvatar from sentry.users.services.user import RpcUser from sentry.utils.http import absolute_uri from sentry.utils.retries import TimedRetryPolicy @@ -337,12 +337,12 @@ def merge_to(from_user: User, to_user: User) -> None: # TODO: we could discover relations automatically and make this useful from sentry.models.auditlogentry import AuditLogEntry from sentry.models.authidentity import AuthIdentity - from sentry.models.avatars.user_avatar import UserAvatar from sentry.models.identity import Identity from sentry.models.options.user_option import UserOption from sentry.models.organizationmembermapping import OrganizationMemberMapping from sentry.models.useremail import UserEmail from sentry.users.models.authenticator import Authenticator + from sentry.users.models.user_avatar import UserAvatar from_user_id = from_user.id to_user_id = to_user.id diff --git a/src/sentry/models/avatars/user_avatar.py b/src/sentry/users/models/user_avatar.py similarity index 93% rename from src/sentry/models/avatars/user_avatar.py rename to src/sentry/users/models/user_avatar.py index df7d44047368a9..db2e86e4faf621 100644 --- a/src/sentry/models/avatars/user_avatar.py +++ b/src/sentry/users/models/user_avatar.py @@ -1,6 +1,7 @@ from __future__ import annotations import contextlib +from collections.abc import Generator from enum import IntEnum from typing import Any, ClassVar, Self @@ -10,17 +11,16 @@ from sentry.db.models.manager.base import BaseManager from sentry.hybridcloud.models.outbox import ControlOutboxBase from sentry.hybridcloud.outbox.category import OutboxCategory +from sentry.models.avatars import ControlAvatarBase from sentry.types.region import find_regions_for_user -from . import ControlAvatarBase - class UserAvatarType(IntEnum): LETTER_AVATAR = 0 UPLOAD = 1 GRAVATAR = 2 - def api_name(self): + def api_name(self) -> str: return self.name.lower() @classmethod @@ -63,7 +63,7 @@ def outboxes_for_update(self, shard_identifier: int | None = None) -> list[Contr ) @contextlib.contextmanager - def _maybe_prepare_outboxes(self, *, outbox_before_super: bool): + def _maybe_prepare_outboxes(self, *, outbox_before_super: bool) -> Generator[None]: from sentry.hybridcloud.models.outbox import outbox_context with outbox_context( @@ -88,5 +88,5 @@ def delete(self, *args: Any, **kwds: Any) -> tuple[int, dict[str, Any]]: with self._maybe_prepare_outboxes(outbox_before_super=True): return super().delete(*args, **kwds) - def get_cache_key(self, size): + def get_cache_key(self, size: int) -> str: return f"avatar:{self.user_id}:{size}" diff --git a/src/sentry/users/services/user/serial.py b/src/sentry/users/services/user/serial.py index 4599b4a1957efb..bedfeef5db89c6 100644 --- a/src/sentry/users/services/user/serial.py +++ b/src/sentry/users/services/user/serial.py @@ -6,8 +6,8 @@ from django.utils.functional import LazyObject from sentry.db.models.manager.base_query_set import BaseQuerySet -from sentry.models.avatars.user_avatar import UserAvatar from sentry.users.models.user import User +from sentry.users.models.user_avatar import UserAvatar from sentry.users.services.user import ( RpcAuthenticator, RpcAvatar, diff --git a/src/sentry/web/frontend/user_avatar.py b/src/sentry/web/frontend/user_avatar.py index af27b9bcf4949f..24875a13d071c6 100644 --- a/src/sentry/web/frontend/user_avatar.py +++ b/src/sentry/web/frontend/user_avatar.py @@ -1,4 +1,4 @@ -from sentry.models.avatars.user_avatar import UserAvatar +from sentry.users.models.user_avatar import UserAvatar from sentry.web.frontend.base import AvatarPhotoView, control_silo_view diff --git a/tests/sentry/api/endpoints/test_user_avatar.py b/tests/sentry/api/endpoints/test_user_avatar.py index 430dafbd437a15..60d505a42544d3 100644 --- a/tests/sentry/api/endpoints/test_user_avatar.py +++ b/tests/sentry/api/endpoints/test_user_avatar.py @@ -4,11 +4,11 @@ from django.urls import reverse from sentry import options as options_store -from sentry.models.avatars.user_avatar import UserAvatar, UserAvatarType from sentry.models.files import ControlFile, File from sentry.silo.base import SiloMode from sentry.testutils.cases import APITestCase from sentry.testutils.silo import assume_test_silo_mode, control_silo_test +from sentry.users.models.user_avatar import UserAvatar, UserAvatarType @control_silo_test diff --git a/tests/sentry/api/serializers/test_user.py b/tests/sentry/api/serializers/test_user.py index a1415c41753b04..6e44376458653f 100644 --- a/tests/sentry/api/serializers/test_user.py +++ b/tests/sentry/api/serializers/test_user.py @@ -3,12 +3,12 @@ from sentry.auth.authenticators import available_authenticators from sentry.models.authidentity import AuthIdentity from sentry.models.authprovider import AuthProvider -from sentry.models.avatars.user_avatar import UserAvatar from sentry.models.useremail import UserEmail from sentry.models.userpermission import UserPermission from sentry.testutils.cases import TestCase from sentry.testutils.silo import control_silo_test from sentry.users.models.authenticator import Authenticator +from sentry.users.models.user_avatar import UserAvatar @control_silo_test diff --git a/tests/sentry/models/test_organization_avatar.py b/tests/sentry/models/test_organization_avatar.py new file mode 100644 index 00000000000000..851d2833f99e98 --- /dev/null +++ b/tests/sentry/models/test_organization_avatar.py @@ -0,0 +1,17 @@ +from sentry.models.avatars.organization_avatar import OrganizationAvatar +from sentry.models.files.file import File +from sentry.testutils.cases import TestCase + + +class OrganizationAvatarTestCase(TestCase): + def test_set_null(self): + org = self.create_organization() + afile = File.objects.create(name="avatar.png", type=OrganizationAvatar.FILE_TYPE) + avatar = OrganizationAvatar.objects.create(organization=org, file_id=afile.id) + + assert avatar.get_file() == afile + + afile.delete() + assert avatar.get_file() is None + assert OrganizationAvatar.objects.get(id=avatar.id).file_id is None + assert OrganizationAvatar.objects.get(id=avatar.id).get_file() is None diff --git a/tests/sentry/models/test_projectownership.py b/tests/sentry/models/test_projectownership.py index c59cb305d11058..12d2251bbe12cf 100644 --- a/tests/sentry/models/test_projectownership.py +++ b/tests/sentry/models/test_projectownership.py @@ -1,6 +1,5 @@ from unittest.mock import patch -from sentry.models.avatars.user_avatar import UserAvatar from sentry.models.groupassignee import GroupAssignee from sentry.models.groupowner import GroupOwner, GroupOwnerType, OwnerRuleType from sentry.models.projectownership import ProjectOwnership @@ -11,6 +10,7 @@ from sentry.testutils.silo import assume_test_silo_mode_of from sentry.testutils.skips import requires_snuba from sentry.types.actor import Actor, ActorType +from sentry.users.models.user_avatar import UserAvatar from sentry.users.services.user.service import user_service pytestmark = requires_snuba diff --git a/tests/sentry/models/test_avatar.py b/tests/sentry/users/models/test_user_avatar.py similarity index 58% rename from tests/sentry/models/test_avatar.py rename to tests/sentry/users/models/test_user_avatar.py index 8aa21174eb8616..64f0b795b77cf3 100644 --- a/tests/sentry/models/test_avatar.py +++ b/tests/sentry/users/models/test_user_avatar.py @@ -1,10 +1,8 @@ from sentry import options as options_store -from sentry.models.avatars.organization_avatar import OrganizationAvatar -from sentry.models.avatars.user_avatar import UserAvatar from sentry.models.files.control_file import ControlFile -from sentry.models.files.file import File from sentry.testutils.cases import TestCase from sentry.testutils.silo import control_silo_test +from sentry.users.models.user_avatar import UserAvatar @control_silo_test @@ -26,17 +24,3 @@ def test_set_null(self): assert avatar.get_file() is None assert UserAvatar.objects.get(id=avatar.id).control_file_id is None assert UserAvatar.objects.get(id=avatar.id).get_file() is None - - -class OrganizationAvatarTestCase(TestCase): - def test_set_null(self): - org = self.create_organization() - afile = File.objects.create(name="avatar.png", type=OrganizationAvatar.FILE_TYPE) - avatar = OrganizationAvatar.objects.create(organization=org, file_id=afile.id) - - assert avatar.get_file() == afile - - afile.delete() - assert avatar.get_file() is None - assert OrganizationAvatar.objects.get(id=avatar.id).file_id is None - assert OrganizationAvatar.objects.get(id=avatar.id).get_file() is None diff --git a/tests/sentry/web/frontend/test_user_avatar.py b/tests/sentry/web/frontend/test_user_avatar.py index 01d247c48191f1..e577dcecf684ac 100644 --- a/tests/sentry/web/frontend/test_user_avatar.py +++ b/tests/sentry/web/frontend/test_user_avatar.py @@ -2,10 +2,10 @@ from django.urls import reverse -from sentry.models.avatars.user_avatar import UserAvatar from sentry.models.files.control_file import ControlFile from sentry.testutils.cases import TestCase from sentry.testutils.silo import control_silo_test +from sentry.users.models.user_avatar import UserAvatar from sentry.web.frontend.generic import FOREVER_CACHE From 0c6b246f2dfbcc774a926b0b0202a8eb2089eab3 Mon Sep 17 00:00:00 2001 From: Christinarlong Date: Wed, 14 Aug 2024 11:02:56 -0700 Subject: [PATCH 2/2] forgot an import spot --- src/sentry/users/services/user/impl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sentry/users/services/user/impl.py b/src/sentry/users/services/user/impl.py index 37f1361221e31f..5e65636a3296b5 100644 --- a/src/sentry/users/services/user/impl.py +++ b/src/sentry/users/services/user/impl.py @@ -21,13 +21,13 @@ from sentry.hybridcloud.services.organization_mapping.model import RpcOrganizationMapping from sentry.hybridcloud.services.organization_mapping.serial import serialize_organization_mapping from sentry.models.authidentity import AuthIdentity -from sentry.models.avatars import UserAvatar from sentry.models.organization import OrganizationStatus from sentry.models.organizationmapping import OrganizationMapping from sentry.models.organizationmembermapping import OrganizationMemberMapping from sentry.models.useremail import UserEmail from sentry.signals import user_signup from sentry.users.models.user import User +from sentry.users.models.user_avatar import UserAvatar from sentry.users.services.user import ( RpcAvatar, RpcUser,