django-models

작성자: sentry

Sentry의 아키텍처 컨벤션(사일로, 복제, 재배치, 외래 키)을 따라 Django ORM 모델을 설계합니다. 새로운 Django 모델을 추가할 때 사용하세요.

npx skills add https://github.com/getsentry/sentry --skill django-models

Sentry Model Conventions

This skill captures the architectural decisions that go into a Sentry model. It does not cover Django syntax, import order, or migration generation — for those:

  • Migrations: invoke the generate-migration skill after the model is designed.
  • Outbox replication plumbing (signal receivers, payload shapes, deletion handlers): invoke the hybrid-cloud-outboxes skill.
  • Hybrid cloud RPCs: invoke the hybrid-cloud-rpc skill.

The four decisions that define a Sentry model

Before writing any fields, decide all four. They are coupled — getting one wrong forces a follow-up migration to fix the others.

1. Which silo does this data live in?

Cell silo (@cell_silo_model) is the default. Use control silo (@control_silo_model) only when the data is shared across organizations or has to be strongly consistent with other control-silo resources (auth, integration installs, API tokens, slug reservations).

The wrong way to think about it: "this is a user-facing thing, so control." The right way: "what is the smallest silo where this can correctly live, and is that silo the same as everything that mutates it together?" If a cell never reads it, it does not belong in the cell.

2. Does the other silo need to see this data?

If yes, the model must inherit from ReplicatedCellModel (cell-side) or ReplicatedControlModel (control-side) and set category: ClassVar[OutboxCategory] = OutboxCategory.MY_THING. The base Model class is for data that genuinely never crosses the silo boundary.

Replication is part of the design, not a thing you bolt on later. If you have to ask "should other silo X be able to look this up by ID without an RPC?" — that is a replication decision, and it changes the base class. When in doubt, defer to the hybrid-cloud-outboxes skill before finalizing the model.

3. Is this data part of an organization export (relocation)?

Every concrete model must set __relocation_scope__ — the runtime check at src/sentry/db/models/base.py raises if missing. The choice is almost always one of:

ScopeWhen
RelocationScope.OrganizationCustomer data tied to an org that should travel with the org during a relocation. Includes most projects, settings, members, dashboards, alerts.
RelocationScope.ExcludedTransient data, telemetry, caches, system-internal state, anything in the getsentry app, or data not meaningful in another instance.

A set of scopes with a get_relocation_scope() override exists but is rare; reach for it only when relocation behavior is per-instance conditional. Excluded cannot be part of a set.

If you are not sure: most new models that store a customer's working state are Organization; most new models that exist for Sentry's operations (queues, caches, attempts, jobs, feature flags consumed at runtime) are Excluded.

4. What is the cross-silo blast radius of each foreign key?

The FK type is an architectural statement, not a style choice:

  • FlexibleForeignKey("sentry.Project", on_delete=...) — the FK target lives in the same silo. A real database constraint is created. Cascading delete is enforced by Postgres.
  • HybridCloudForeignKey("sentry.User", on_delete="...", ...) — the FK target lives in the opposite silo. No database constraint. Cascade is eventually consistent via outbox tombstones. on_delete is passed as a string ("CASCADE", "SET_NULL", "DO_NOTHING"). Name the field with an explicit _id suffix (e.g. user_id = HybridCloudForeignKey(...)) since there's no ORM relationship to resolve — only an ID. Compare with FlexibleForeignKey, where Django gives you both project (the related object) and project_id (the column) from a single project = FlexibleForeignKey(...).
  • Plain Django ForeignKey — avoid. A couple of older workflow_engine models still use it, but for new code the convention is FlexibleForeignKey (same-silo) or HybridCloudForeignKey (cross-silo). Both plug into Sentry's deletion framework and hybrid-cloud plumbing in ways plain ForeignKey does not.

If a model has both kinds of FKs, that is fine and common. The presence of an HCFK is not a signal that the model should be in the other silo — it just means the relationship crosses silos.

Other norms worth encoding

Base class and timestamps

Use DefaultFieldsModel for new models. It gives you date_added (auto_now_add=True) and date_updated (auto_now=True) for free, and it's almost always what you want — tables that genuinely shouldn't track either timestamp are rare. DefaultFieldsModelExisting is legacy-only — its docstring explicitly says don't use it on new models (it leaves date_added nullable for backward compat with models that predate the field).

Field-type intent

  • BoundedBigAutoField for primary keys, BoundedBigIntegerField / BoundedPositiveIntegerField for non-PK numeric IDs and counts. The "bounded" part is a runtime overflow guard, not a Django nicety — it catches values that would silently corrupt downstream consumers.
  • For bounded text fields without a strict spec, prefer CharField(max_length=256). Postgres varchar(n) storage is identical regardless of n below the TOAST threshold, so picking 64 prematurely is a constraint without a benefit. Pick a smaller value only when the column has a real meaning (hash=40/64, UUID=32, slug, identifier with a spec).
  • For free-form text fields, use TextField.
  • For new JSON columns, prefer Django's models.JSONField() (jsonb-backed). The legacy sentry.db.models.fields.jsonfield.JSONField is text-backed and exists for compatibility with old columns — only use it when intentionally matching one.
  • Mutable callable defaults (default=dict, default=list) — never bare default={} / default=[].

Soft delete is explicit

There is no metaclass-based soft delete. If a model needs soft delete, add an explicit status field using ObjectStatus and have business logic respect it. ParanoidModel exists (SentryAppInstallation uses it) but it is a heavyweight choice — most models should not need it.

Constraints over unique_together

For new models, use Meta.constraints = [UniqueConstraint(...)] instead of unique_together. The reason is condition=Q(...): partial unique constraints are the only correct way to express "unique when this column is not null", which is a common requirement and silently broken with unique_together. Constraint and index names should be explicit and descriptive, not auto-generated.

Composite indexes match query order

If you filter on (org_id, project_id, type) together, you need an index whose field order matches the most-selective-first filter pattern. A foreign key auto-index does not cover the multi-column case.

Where the file lives

  • Default: put new models in an app under src/sentry/<app>/models.py (single file) or src/sentry/<app>/models/<thing>.py (one model per file) — pick by precedent in the app.
  • src/sentry/models/<thing>.py is the legacy location. Only put a new model there if it's tightly coupled to a model that already lives there and moving it would be disruptive.
  • New apps under src/sentry/<app>/ should set up a real Django app (apps.py, __init__.py with default_app_config).

A minimal cell-silo model skeleton

This is a starting scaffold, not a template — strip what you do not need, do not add things you do not need.

from __future__ import annotations

from django.db import models

from sentry.backup.scopes import RelocationScope
from sentry.db.models import DefaultFieldsModel, FlexibleForeignKey, cell_silo_model, sane_repr
from sentry.db.models.fields.hybrid_cloud_foreign_key import HybridCloudForeignKey


@cell_silo_model
class MyThing(DefaultFieldsModel):
    __relocation_scope__ = RelocationScope.Organization

    organization = FlexibleForeignKey("sentry.Organization")
    project = FlexibleForeignKey("sentry.Project")
    user_id = HybridCloudForeignKey("sentry.User", null=True, on_delete="SET_NULL")

    name = models.CharField(max_length=256)
    config = models.JSONField(default=dict)

    class Meta:
        app_label = "sentry"
        db_table = "sentry_mything"
        constraints = [
            models.UniqueConstraint(
                fields=["organization", "name"],
                name="sentry_mything_org_name_unique",
            ),
        ]

    __repr__ = sane_repr("organization_id", "project_id", "name")

For a control-silo model, swap @cell_silo_model for @control_silo_model. For a model whose state needs to be visible from the other silo, change the base to ReplicatedCellModel or ReplicatedControlModel and add category = OutboxCategory.MY_THING — then invoke the hybrid-cloud-outboxes skill for the rest.

After the model is designed

  1. Generate the migration: invoke the generate-migration skill.
  2. If the model is replicated: invoke the hybrid-cloud-outboxes skill to wire up payload, signal receivers, and deletion handlers.
  3. If the app's models/__init__.py re-exports its models, add your new one there too — it's a convenience for callers (from sentry.<app>.models import Thing), not a Django requirement, so match the app's precedent.

sentry의 다른 스킬

generate-frontend-forms
sentry
Sentry의 새로운 폼 시스템을 사용하여 폼을 생성하는 가이드입니다. 폼, 폼 필드, 유효성 검사 또는 자동 저장 기능을 구현할 때 사용하세요.
official
sentry-snapshots-cocoa
sentry
Apple/Cocoa 프로젝트를 위한 전체 Sentry Snapshots 설정입니다. "SnapshotPreviews 설정", "Apple 스냅샷 테스트 설정", "Apple 스냅샷 업로드" 요청 시 사용하세요.
official
architecture-review
sentry
직원 수준의 코드베이스 건강 검토. 모놀리식 모듈, 무음 실패, 타입 안전성 격차, 테스트 커버리지 구멍, LLM 친화성 문제를 찾습니다.
official
linear-type-labeler
sentry
Linear 이슈를 분류하고, 각 이슈의 제목과 설명 내용을 기반으로 Sentry 워크스페이스의 레이블 분류 체계에서 Type 레이블을 적용합니다.
official
sentry-flutter-sdk
sentry
Flutter 및 Dart를 위한 완전한 Sentry SDK 설정입니다. "Flutter에 Sentry 추가", "sentry_flutter 설치", "Dart에서 Sentry 설정" 또는 오류 구성을 요청받았을 때 사용하세요.
official
sentry-svelte-sdk
sentry
Svelte 및 SvelteKit을 위한 완전한 Sentry SDK 설정입니다. "Svelte에 Sentry 추가", "SvelteKit에 Sentry 추가", "@sentry/sveltekit 설치" 또는 구성 요청 시 사용하세요.
official
vercel-react-best-practices
sentry
Vercel Engineering의 React 및 Next.js 성능 최적화 가이드라인입니다. 이 스킬은 React/Next.js 코드를 작성, 검토 또는 리팩토링할 때 사용해야 합니다.
official
sentry-tanstack-start-sdk
sentry
TanStack Start React용 전체 Sentry SDK 설정. "TanStack Start에 Sentry 추가", "@sentry/tanstackstart-react 설치" 또는 오류 구성 요청 시 사용…
official