Skip to content

ref(control_silo): Move Email model to Users module #75915

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/sentry/backup/imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,10 @@ def _import(
"""

# Import here to prevent circular module resolutions.
from sentry.models.email import Email
from sentry.models.organization import Organization
from sentry.models.organizationmember import OrganizationMember
from sentry.models.user import User
from sentry.users.models.email import Email

if SiloMode.get_current_mode() == SiloMode.CONTROL:
errText = "Imports must be run in REGION or MONOLITH instances only"
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from ..users.models.email import * # NOQA
from .activity import * # NOQA
from .apiapplication import * # NOQA
from .apiauthorization import * # NOQA
Expand Down Expand Up @@ -29,7 +30,6 @@
from .deploy import * # NOQA
from .distribution import * # NOQA
from .dynamicsampling import * # NOQA
from .email import * # NOQA
from .environment import * # NOQA
from .event import * # NOQA
from .eventattachment import * # NOQA
Expand Down
59 changes: 2 additions & 57 deletions src/sentry/models/email.py
Original file line number Diff line number Diff line change
@@ -1,58 +1,3 @@
from __future__ import annotations
from sentry.users.models.email import Email

from django.db import models
from django.forms import model_to_dict
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from sentry.backup.dependencies import ImportKind, PrimaryKeyMap, get_model_name
from sentry.backup.helpers import ImportFlags
from sentry.backup.scopes import ImportScope, RelocationScope
from sentry.db.models import CIEmailField, Model, control_silo_model, sane_repr


@control_silo_model
class Email(Model):
"""
Email represents a unique email. Email settings (unsubscribe state) should be associated here.
UserEmail represents whether a given user account has access to that email.
"""

__relocation_scope__ = RelocationScope.User
__relocation_dependencies__ = {"sentry.User"}
__relocation_custom_ordinal__ = ["email"]

email = CIEmailField(_("email address"), unique=True, max_length=75)
date_added = models.DateTimeField(default=timezone.now)

class Meta:
app_label = "sentry"
db_table = "sentry_email"

__repr__ = sane_repr("email")

@classmethod
def query_for_relocation_export(cls, q: models.Q, pk_map: PrimaryKeyMap) -> models.Q:
from sentry.models.user import User
from sentry.models.useremail import UserEmail

# `Sentry.Email` models don't have any explicit dependencies on `Sentry.User`, so we need to
# find them manually via `UserEmail`.
emails = UserEmail.objects.filter(
user_id__in=pk_map.get_pks(get_model_name(User))
).values_list("email", flat=True)

return q & models.Q(email__in=emails)

def write_relocation_import(
self, _s: ImportScope, _f: ImportFlags
) -> tuple[int, ImportKind] | None:
# Ensure that we never attempt to duplicate email entries, as they must always be unique.
(email, created) = self.__class__.objects.get_or_create(
email=self.email, defaults=model_to_dict(self)
)
if email:
self.pk = email.pk
self.save()

return (self.pk, ImportKind.Inserted if created else ImportKind.Existing)
__all__ = ("Email",)
2 changes: 1 addition & 1 deletion src/sentry/receivers/email.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from django.db import IntegrityError, router, transaction
from django.db.models.signals import post_delete, post_save

from sentry.models.email import Email
from sentry.models.useremail import UserEmail
from sentry.users.models.email import Email


def create_email(instance, created, **kwargs):
Expand Down
58 changes: 58 additions & 0 deletions src/sentry/users/models/email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from __future__ import annotations

from django.db import models
from django.forms import model_to_dict
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from sentry.backup.dependencies import ImportKind, PrimaryKeyMap, get_model_name
from sentry.backup.helpers import ImportFlags
from sentry.backup.scopes import ImportScope, RelocationScope
from sentry.db.models import CIEmailField, Model, control_silo_model, sane_repr


@control_silo_model
class Email(Model):
"""
Email represents a unique email. Email settings (unsubscribe state) should be associated here.
UserEmail represents whether a given user account has access to that email.
"""

__relocation_scope__ = RelocationScope.User
__relocation_dependencies__ = {"sentry.User"}
__relocation_custom_ordinal__ = ["email"]

email = CIEmailField(_("email address"), unique=True, max_length=75)
date_added = models.DateTimeField(default=timezone.now)

class Meta:
app_label = "sentry"
db_table = "sentry_email"

__repr__ = sane_repr("email")

@classmethod
def query_for_relocation_export(cls, q: models.Q, pk_map: PrimaryKeyMap) -> models.Q:
from sentry.models.user import User
from sentry.models.useremail import UserEmail

# `Sentry.Email` models don't have any explicit dependencies on `Sentry.User`, so we need to
# find them manually via `UserEmail`.
emails = UserEmail.objects.filter(
user_id__in=pk_map.get_pks(get_model_name(User))
).values_list("email", flat=True)

return q & models.Q(email__in=emails)

def write_relocation_import(
self, _s: ImportScope, _f: ImportFlags
) -> tuple[int, ImportKind] | None:
# Ensure that we never attempt to duplicate email entries, as they must always be unique.
(email, created) = self.__class__.objects.get_or_create(
email=self.email, defaults=model_to_dict(self)
)
if email:
self.pk = email.pk
self.save()

return (self.pk, ImportKind.Inserted if created else ImportKind.Existing)
2 changes: 1 addition & 1 deletion tests/sentry/backup/test_exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from sentry.backup.scopes import ExportScope
from sentry.backup.validate import validate
from sentry.db import models
from sentry.models.email import Email
from sentry.models.options.option import Option
from sentry.models.organization import Organization
from sentry.models.organizationmember import OrganizationMember
Expand All @@ -26,6 +25,7 @@
export_to_file,
)
from sentry.testutils.helpers.datetime import freeze_time
from sentry.users.models.email import Email
from tests.sentry.backup import get_matching_exportable_models


Expand Down
2 changes: 1 addition & 1 deletion tests/sentry/backup/test_findings.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
RpcImportError,
RpcImportErrorKind,
)
from sentry.models.email import Email
from sentry.testutils.cases import TestCase
from sentry.users.models.email import Email

encoder = FindingJSONEncoder(
sort_keys=True,
Expand Down
2 changes: 1 addition & 1 deletion tests/sentry/backup/test_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
from sentry.backup.services.import_export.model import RpcImportErrorKind
from sentry.models.apitoken import DEFAULT_EXPIRATION, ApiToken, generate_token
from sentry.models.authenticator import Authenticator
from sentry.models.email import Email
from sentry.models.importchunk import (
ControlImportChunk,
ControlImportChunkReplica,
Expand Down Expand Up @@ -81,6 +80,7 @@
)
from sentry.testutils.hybrid_cloud import use_split_dbs
from sentry.testutils.silo import assume_test_silo_mode
from sentry.users.models.email import Email
from tests.sentry.backup import (
expect_models,
get_matching_exportable_models,
Expand Down
2 changes: 1 addition & 1 deletion tests/sentry/runner/commands/test_backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from sentry.backup.findings import InstanceID
from sentry.backup.imports import ImportingError
from sentry.backup.services.import_export.model import RpcImportError, RpcImportErrorKind
from sentry.models.email import Email
from sentry.runner.commands.backup import backup, export, import_
from sentry.silo.base import SiloMode
from sentry.testutils.cases import TestCase, TransactionTestCase
Expand All @@ -36,6 +35,7 @@
generate_rsa_key_pair,
)
from sentry.testutils.silo import assume_test_silo_mode
from sentry.users.models.email import Email
from sentry.utils import json

GOOD_FILE_PATH = get_fixture_path("backup", "fresh-install.json")
Expand Down
Loading