We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Can't add a new column to Organization
### Environment

self-hosted (https://develop.sentry.dev/self-hosted/)

### Version

_No response_

### Steps to Reproduce

1. Add a new column to organization
2. Try to make a new migration using `sentry django makemigration`

### Expected Result

Makemigration should generate a new migration file.

### Actual Result

Generation fails due to makemigrations loading the SDK.  This in turn hits this line of code for feature checking:
https://github.com/getsentry/sentry/blob/ad99c1fe7d7378c3761bad757aacf27352c693a6/src/sentry/models/projectkey.py#L249-L259



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/sentry/models/projectkey.py`
Content:
```
1 import re
2 from urllib.parse import urlparse
3 from uuid import uuid4
4 
5 import petname
6 from django.conf import settings
7 from django.db import models, transaction
8 from django.urls import reverse
9 from django.utils import timezone
10 from django.utils.translation import ugettext_lazy as _
11 
12 from bitfield import BitField
13 from sentry import features, options
14 from sentry.db.models import (
15     BaseManager,
16     BoundedPositiveIntegerField,
17     FlexibleForeignKey,
18     JSONField,
19     Model,
20     region_silo_model,
21     sane_repr,
22 )
23 from sentry.tasks.relay import schedule_invalidate_project_config
24 
25 _uuid4_re = re.compile(r"^[a-f0-9]{32}$")
26 
27 # TODO(dcramer): pull in enum library
28 
29 
30 class ProjectKeyStatus:
31     ACTIVE = 0
32     INACTIVE = 1
33 
34 
35 class ProjectKeyManager(BaseManager):
36     def post_save(self, instance, **kwargs):
37         # this hook may be called from model hooks during an
38         # open transaction. In that case, wait until the current transaction has
39         # been committed or rolled back to ensure we don't read stale data in the
40         # task.
41         #
42         # If there is no transaction open, on_commit should run immediately.
43         transaction.on_commit(
44             lambda: schedule_invalidate_project_config(
45                 public_key=instance.public_key, trigger="projectkey.post_save"
46             )
47         )
48 
49     def post_delete(self, instance, **kwargs):
50         # this hook may be called from model hooks during an
51         # open transaction. In that case, wait until the current transaction has
52         # been committed or rolled back to ensure we don't read stale data in the
53         # task.
54         #
55         # If there is no transaction open, on_commit should run immediately.
56         transaction.on_commit(
57             lambda: schedule_invalidate_project_config(
58                 public_key=instance.public_key, trigger="projectkey.post_delete"
59             )
60         )
61 
62 
63 @region_silo_model
64 class ProjectKey(Model):
65     __include_in_export__ = True
66 
67     project = FlexibleForeignKey("sentry.Project", related_name="key_set")
68     label = models.CharField(max_length=64, blank=True, null=True)
69     public_key = models.CharField(max_length=32, unique=True, null=True)
70     secret_key = models.CharField(max_length=32, unique=True, null=True)
71     roles = BitField(
72         flags=(
73             # access to post events to the store endpoint
74             ("store", "Event API access"),
75             # read/write access to rest API
76             ("api", "Web API access"),
77         ),
78         default=["store"],
79     )
80     status = BoundedPositiveIntegerField(
81         default=0,
82         choices=(
83             (ProjectKeyStatus.ACTIVE, _("Active")),
84             (ProjectKeyStatus.INACTIVE, _("Inactive")),
85         ),
86         db_index=True,
87     )
88     date_added = models.DateTimeField(default=timezone.now, null=True)
89 
90     rate_limit_count = BoundedPositiveIntegerField(null=True)
91     rate_limit_window = BoundedPositiveIntegerField(null=True)
92 
93     objects = ProjectKeyManager(
94         cache_fields=("public_key", "secret_key"),
95         # store projectkeys in memcached for longer than other models,
96         # specifically to make the relay_projectconfig endpoint faster.
97         cache_ttl=60 * 30,
98     )
99 
100     data = JSONField()
101 
102     # support legacy project keys in API
103     scopes = (
104         "project:read",
105         "project:write",
106         "project:admin",
107         "project:releases",
108         "event:read",
109         "event:write",
110         "event:admin",
111     )
112 
113     class Meta:
114         app_label = "sentry"
115         db_table = "sentry_projectkey"
116 
117     __repr__ = sane_repr("project_id", "public_key")
118 
119     def __str__(self):
120         return str(self.public_key)
121 
122     @classmethod
123     def generate_api_key(cls):
124         return uuid4().hex
125 
126     @classmethod
127     def looks_like_api_key(cls, key):
128         return bool(_uuid4_re.match(key))
129 
130     @classmethod
131     def from_dsn(cls, dsn):
132         urlparts = urlparse(dsn)
133 
134         public_key = urlparts.username
135         project_id = urlparts.path.rsplit("/", 1)[-1]
136 
137         try:
138             return ProjectKey.objects.get(public_key=public_key, project=project_id)
139         except ValueError:
140             # ValueError would come from a non-integer project_id,
141             # which is obviously a DoesNotExist. We catch and rethrow this
142             # so anything downstream expecting DoesNotExist works fine
143             raise ProjectKey.DoesNotExist("ProjectKey matching query does not exist.")
144 
145     @classmethod
146     def get_default(cls, project):
147         return cls.objects.filter(
148             project=project,
149             roles=models.F("roles").bitor(cls.roles.store),
150             status=ProjectKeyStatus.ACTIVE,
151         ).first()
152 
153     @property
154     def is_active(self):
155         return self.status == ProjectKeyStatus.ACTIVE
156 
157     @property
158     def rate_limit(self):
159         if self.rate_limit_count and self.rate_limit_window:
160             return (self.rate_limit_count, self.rate_limit_window)
161         return (0, 0)
162 
163     def save(self, *args, **kwargs):
164         if not self.public_key:
165             self.public_key = ProjectKey.generate_api_key()
166         if not self.secret_key:
167             self.secret_key = ProjectKey.generate_api_key()
168         if not self.label:
169             self.label = petname.Generate(2, " ", letters=10).title()
170         super().save(*args, **kwargs)
171 
172     def get_dsn(self, domain=None, secure=True, public=False):
173         urlparts = urlparse(self.get_endpoint(public=public))
174 
175         if not public:
176             key = f"{self.public_key}:{self.secret_key}"
177         else:
178             key = self.public_key
179 
180         # If we do not have a scheme or domain/hostname, dsn is never valid
181         if not urlparts.netloc or not urlparts.scheme:
182             return ""
183 
184         return "{}://{}@{}/{}".format(
185             urlparts.scheme,
186             key,
187             urlparts.netloc + urlparts.path,
188             self.project_id,
189         )
190 
191     @property
192     def organization_id(self):
193         return self.project.organization_id
194 
195     @property
196     def organization(self):
197         return self.project.organization
198 
199     @property
200     def dsn_private(self):
201         return self.get_dsn(public=False)
202 
203     @property
204     def dsn_public(self):
205         return self.get_dsn(public=True)
206 
207     @property
208     def csp_endpoint(self):
209         endpoint = self.get_endpoint()
210 
211         return f"{endpoint}/api/{self.project_id}/csp-report/?sentry_key={self.public_key}"
212 
213     @property
214     def security_endpoint(self):
215         endpoint = self.get_endpoint()
216 
217         return f"{endpoint}/api/{self.project_id}/security/?sentry_key={self.public_key}"
218 
219     @property
220     def minidump_endpoint(self):
221         endpoint = self.get_endpoint()
222 
223         return f"{endpoint}/api/{self.project_id}/minidump/?sentry_key={self.public_key}"
224 
225     @property
226     def unreal_endpoint(self):
227         return f"{self.get_endpoint()}/api/{self.project_id}/unreal/{self.public_key}/"
228 
229     @property
230     def js_sdk_loader_cdn_url(self):
231         if settings.JS_SDK_LOADER_CDN_URL:
232             return f"{settings.JS_SDK_LOADER_CDN_URL}{self.public_key}.min.js"
233         else:
234             endpoint = self.get_endpoint()
235             return "{}{}".format(
236                 endpoint,
237                 reverse("sentry-js-sdk-loader", args=[self.public_key, ".min"]),
238             )
239 
240     def get_endpoint(self, public=True):
241         if public:
242             endpoint = settings.SENTRY_PUBLIC_ENDPOINT or settings.SENTRY_ENDPOINT
243         else:
244             endpoint = settings.SENTRY_ENDPOINT
245 
246         if not endpoint:
247             endpoint = options.get("system.url-prefix")
248 
249         if features.has("organizations:org-subdomains", self.project.organization):
250             urlparts = urlparse(endpoint)
251             if urlparts.scheme and urlparts.netloc:
252                 endpoint = "{}://{}.{}{}".format(
253                     urlparts.scheme,
254                     settings.SENTRY_ORG_SUBDOMAIN_TEMPLATE.format(
255                         organization_id=self.project.organization_id
256                     ),
257                     urlparts.netloc,
258                     urlparts.path,
259                 )
260 
261         return endpoint
262 
263     def get_allowed_origins(self):
264         from sentry.utils.http import get_origins
265 
266         return get_origins(self.project)
267 
268     def get_audit_log_data(self):
269         return {
270             "label": self.label,
271             "public_key": self.public_key,
272             "secret_key": self.secret_key,
273             "roles": int(self.roles),
274             "status": self.status,
275             "rate_limit_count": self.rate_limit_count,
276             "rate_limit_window": self.rate_limit_window,
277         }
278 
279     def get_scopes(self):
280         return self.scopes
281
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/sentry/models/projectkey.py b/src/sentry/models/projectkey.py
--- a/src/sentry/models/projectkey.py
+++ b/src/sentry/models/projectkey.py
@@ -4,7 +4,7 @@
 
 import petname
 from django.conf import settings
-from django.db import models, transaction
+from django.db import ProgrammingError, models, transaction
 from django.urls import reverse
 from django.utils import timezone
 from django.utils.translation import ugettext_lazy as _
@@ -246,7 +246,16 @@
         if not endpoint:
             endpoint = options.get("system.url-prefix")
 
-        if features.has("organizations:org-subdomains", self.project.organization):
+        has_org_subdomain = False
+        try:
+            has_org_subdomain = features.has(
+                "organizations:org-subdomains", self.project.organization
+            )
+        except ProgrammingError:
+            # This happens during migration generation for the organization model.
+            pass
+
+        if has_org_subdomain:
             urlparts = urlparse(endpoint)
             if urlparts.scheme and urlparts.netloc:
                 endpoint = "{}://{}.{}{}".format(

{"golden_diff": "diff --git a/src/sentry/models/projectkey.py b/src/sentry/models/projectkey.py\n--- a/src/sentry/models/projectkey.py\n+++ b/src/sentry/models/projectkey.py\n@@ -4,7 +4,7 @@\n \n import petname\n from django.conf import settings\n-from django.db import models, transaction\n+from django.db import ProgrammingError, models, transaction\n from django.urls import reverse\n from django.utils import timezone\n from django.utils.translation import ugettext_lazy as _\n@@ -246,7 +246,16 @@\n         if not endpoint:\n             endpoint = options.get(\"system.url-prefix\")\n \n-        if features.has(\"organizations:org-subdomains\", self.project.organization):\n+        has_org_subdomain = False\n+        try:\n+            has_org_subdomain = features.has(\n+                \"organizations:org-subdomains\", self.project.organization\n+            )\n+        except ProgrammingError:\n+            # This happens during migration generation for the organization model.\n+            pass\n+\n+        if has_org_subdomain:\n             urlparts = urlparse(endpoint)\n             if urlparts.scheme and urlparts.netloc:\n                 endpoint = \"{}://{}.{}{}\".format(\n", "issue": "Can't add a new column to Organization\n### Environment\n\nself-hosted (https://develop.sentry.dev/self-hosted/)\n\n### Version\n\n_No response_\n\n### Steps to Reproduce\n\n1. Add a new column to organization\r\n2. Try to make a new migration using `sentry django makemigration`\n\n### Expected Result\n\nMakemigration should generate a new migration file.\n\n### Actual Result\n\nGeneration fails due to makemigrations loading the SDK.  This in turn hits this line of code for feature checking:\r\nhttps://github.com/getsentry/sentry/blob/ad99c1fe7d7378c3761bad757aacf27352c693a6/src/sentry/models/projectkey.py#L249-L259\r\n\r\n\n", "before_files": [{"content": "import re\nfrom urllib.parse import urlparse\nfrom uuid import uuid4\n\nimport petname\nfrom django.conf import settings\nfrom django.db import models, transaction\nfrom django.urls import reverse\nfrom django.utils import timezone\nfrom django.utils.translation import ugettext_lazy as _\n\nfrom bitfield import BitField\nfrom sentry import features, options\nfrom sentry.db.models import (\n    BaseManager,\n    BoundedPositiveIntegerField,\n    FlexibleForeignKey,\n    JSONField,\n    Model,\n    region_silo_model,\n    sane_repr,\n)\nfrom sentry.tasks.relay import schedule_invalidate_project_config\n\n_uuid4_re = re.compile(r\"^[a-f0-9]{32}$\")\n\n# TODO(dcramer): pull in enum library\n\n\nclass ProjectKeyStatus:\n    ACTIVE = 0\n    INACTIVE = 1\n\n\nclass ProjectKeyManager(BaseManager):\n    def post_save(self, instance, **kwargs):\n        # this hook may be called from model hooks during an\n        # open transaction. In that case, wait until the current transaction has\n        # been committed or rolled back to ensure we don't read stale data in the\n        # task.\n        #\n        # If there is no transaction open, on_commit should run immediately.\n        transaction.on_commit(\n            lambda: schedule_invalidate_project_config(\n                public_key=instance.public_key, trigger=\"projectkey.post_save\"\n            )\n        )\n\n    def post_delete(self, instance, **kwargs):\n        # this hook may be called from model hooks during an\n        # open transaction. In that case, wait until the current transaction has\n        # been committed or rolled back to ensure we don't read stale data in the\n        # task.\n        #\n        # If there is no transaction open, on_commit should run immediately.\n        transaction.on_commit(\n            lambda: schedule_invalidate_project_config(\n                public_key=instance.public_key, trigger=\"projectkey.post_delete\"\n            )\n        )\n\n\n@region_silo_model\nclass ProjectKey(Model):\n    __include_in_export__ = True\n\n    project = FlexibleForeignKey(\"sentry.Project\", related_name=\"key_set\")\n    label = models.CharField(max_length=64, blank=True, null=True)\n    public_key = models.CharField(max_length=32, unique=True, null=True)\n    secret_key = models.CharField(max_length=32, unique=True, null=True)\n    roles = BitField(\n        flags=(\n            # access to post events to the store endpoint\n            (\"store\", \"Event API access\"),\n            # read/write access to rest API\n            (\"api\", \"Web API access\"),\n        ),\n        default=[\"store\"],\n    )\n    status = BoundedPositiveIntegerField(\n        default=0,\n        choices=(\n            (ProjectKeyStatus.ACTIVE, _(\"Active\")),\n            (ProjectKeyStatus.INACTIVE, _(\"Inactive\")),\n        ),\n        db_index=True,\n    )\n    date_added = models.DateTimeField(default=timezone.now, null=True)\n\n    rate_limit_count = BoundedPositiveIntegerField(null=True)\n    rate_limit_window = BoundedPositiveIntegerField(null=True)\n\n    objects = ProjectKeyManager(\n        cache_fields=(\"public_key\", \"secret_key\"),\n        # store projectkeys in memcached for longer than other models,\n        # specifically to make the relay_projectconfig endpoint faster.\n        cache_ttl=60 * 30,\n    )\n\n    data = JSONField()\n\n    # support legacy project keys in API\n    scopes = (\n        \"project:read\",\n        \"project:write\",\n        \"project:admin\",\n        \"project:releases\",\n        \"event:read\",\n        \"event:write\",\n        \"event:admin\",\n    )\n\n    class Meta:\n        app_label = \"sentry\"\n        db_table = \"sentry_projectkey\"\n\n    __repr__ = sane_repr(\"project_id\", \"public_key\")\n\n    def __str__(self):\n        return str(self.public_key)\n\n    @classmethod\n    def generate_api_key(cls):\n        return uuid4().hex\n\n    @classmethod\n    def looks_like_api_key(cls, key):\n        return bool(_uuid4_re.match(key))\n\n    @classmethod\n    def from_dsn(cls, dsn):\n        urlparts = urlparse(dsn)\n\n        public_key = urlparts.username\n        project_id = urlparts.path.rsplit(\"/\", 1)[-1]\n\n        try:\n            return ProjectKey.objects.get(public_key=public_key, project=project_id)\n        except ValueError:\n            # ValueError would come from a non-integer project_id,\n            # which is obviously a DoesNotExist. We catch and rethrow this\n            # so anything downstream expecting DoesNotExist works fine\n            raise ProjectKey.DoesNotExist(\"ProjectKey matching query does not exist.\")\n\n    @classmethod\n    def get_default(cls, project):\n        return cls.objects.filter(\n            project=project,\n            roles=models.F(\"roles\").bitor(cls.roles.store),\n            status=ProjectKeyStatus.ACTIVE,\n        ).first()\n\n    @property\n    def is_active(self):\n        return self.status == ProjectKeyStatus.ACTIVE\n\n    @property\n    def rate_limit(self):\n        if self.rate_limit_count and self.rate_limit_window:\n            return (self.rate_limit_count, self.rate_limit_window)\n        return (0, 0)\n\n    def save(self, *args, **kwargs):\n        if not self.public_key:\n            self.public_key = ProjectKey.generate_api_key()\n        if not self.secret_key:\n            self.secret_key = ProjectKey.generate_api_key()\n        if not self.label:\n            self.label = petname.Generate(2, \" \", letters=10).title()\n        super().save(*args, **kwargs)\n\n    def get_dsn(self, domain=None, secure=True, public=False):\n        urlparts = urlparse(self.get_endpoint(public=public))\n\n        if not public:\n            key = f\"{self.public_key}:{self.secret_key}\"\n        else:\n            key = self.public_key\n\n        # If we do not have a scheme or domain/hostname, dsn is never valid\n        if not urlparts.netloc or not urlparts.scheme:\n            return \"\"\n\n        return \"{}://{}@{}/{}\".format(\n            urlparts.scheme,\n            key,\n            urlparts.netloc + urlparts.path,\n            self.project_id,\n        )\n\n    @property\n    def organization_id(self):\n        return self.project.organization_id\n\n    @property\n    def organization(self):\n        return self.project.organization\n\n    @property\n    def dsn_private(self):\n        return self.get_dsn(public=False)\n\n    @property\n    def dsn_public(self):\n        return self.get_dsn(public=True)\n\n    @property\n    def csp_endpoint(self):\n        endpoint = self.get_endpoint()\n\n        return f\"{endpoint}/api/{self.project_id}/csp-report/?sentry_key={self.public_key}\"\n\n    @property\n    def security_endpoint(self):\n        endpoint = self.get_endpoint()\n\n        return f\"{endpoint}/api/{self.project_id}/security/?sentry_key={self.public_key}\"\n\n    @property\n    def minidump_endpoint(self):\n        endpoint = self.get_endpoint()\n\n        return f\"{endpoint}/api/{self.project_id}/minidump/?sentry_key={self.public_key}\"\n\n    @property\n    def unreal_endpoint(self):\n        return f\"{self.get_endpoint()}/api/{self.project_id}/unreal/{self.public_key}/\"\n\n    @property\n    def js_sdk_loader_cdn_url(self):\n        if settings.JS_SDK_LOADER_CDN_URL:\n            return f\"{settings.JS_SDK_LOADER_CDN_URL}{self.public_key}.min.js\"\n        else:\n            endpoint = self.get_endpoint()\n            return \"{}{}\".format(\n                endpoint,\n                reverse(\"sentry-js-sdk-loader\", args=[self.public_key, \".min\"]),\n            )\n\n    def get_endpoint(self, public=True):\n        if public:\n            endpoint = settings.SENTRY_PUBLIC_ENDPOINT or settings.SENTRY_ENDPOINT\n        else:\n            endpoint = settings.SENTRY_ENDPOINT\n\n        if not endpoint:\n            endpoint = options.get(\"system.url-prefix\")\n\n        if features.has(\"organizations:org-subdomains\", self.project.organization):\n            urlparts = urlparse(endpoint)\n            if urlparts.scheme and urlparts.netloc:\n                endpoint = \"{}://{}.{}{}\".format(\n                    urlparts.scheme,\n                    settings.SENTRY_ORG_SUBDOMAIN_TEMPLATE.format(\n                        organization_id=self.project.organization_id\n                    ),\n                    urlparts.netloc,\n                    urlparts.path,\n                )\n\n        return endpoint\n\n    def get_allowed_origins(self):\n        from sentry.utils.http import get_origins\n\n        return get_origins(self.project)\n\n    def get_audit_log_data(self):\n        return {\n            \"label\": self.label,\n            \"public_key\": self.public_key,\n            \"secret_key\": self.secret_key,\n            \"roles\": int(self.roles),\n            \"status\": self.status,\n            \"rate_limit_count\": self.rate_limit_count,\n            \"rate_limit_window\": self.rate_limit_window,\n        }\n\n    def get_scopes(self):\n        return self.scopes\n", "path": "src/sentry/models/projectkey.py"}], "after_files": [{"content": "import re\nfrom urllib.parse import urlparse\nfrom uuid import uuid4\n\nimport petname\nfrom django.conf import settings\nfrom django.db import ProgrammingError, models, transaction\nfrom django.urls import reverse\nfrom django.utils import timezone\nfrom django.utils.translation import ugettext_lazy as _\n\nfrom bitfield import BitField\nfrom sentry import features, options\nfrom sentry.db.models import (\n    BaseManager,\n    BoundedPositiveIntegerField,\n    FlexibleForeignKey,\n    JSONField,\n    Model,\n    region_silo_model,\n    sane_repr,\n)\nfrom sentry.tasks.relay import schedule_invalidate_project_config\n\n_uuid4_re = re.compile(r\"^[a-f0-9]{32}$\")\n\n# TODO(dcramer): pull in enum library\n\n\nclass ProjectKeyStatus:\n    ACTIVE = 0\n    INACTIVE = 1\n\n\nclass ProjectKeyManager(BaseManager):\n    def post_save(self, instance, **kwargs):\n        # this hook may be called from model hooks during an\n        # open transaction. In that case, wait until the current transaction has\n        # been committed or rolled back to ensure we don't read stale data in the\n        # task.\n        #\n        # If there is no transaction open, on_commit should run immediately.\n        transaction.on_commit(\n            lambda: schedule_invalidate_project_config(\n                public_key=instance.public_key, trigger=\"projectkey.post_save\"\n            )\n        )\n\n    def post_delete(self, instance, **kwargs):\n        # this hook may be called from model hooks during an\n        # open transaction. In that case, wait until the current transaction has\n        # been committed or rolled back to ensure we don't read stale data in the\n        # task.\n        #\n        # If there is no transaction open, on_commit should run immediately.\n        transaction.on_commit(\n            lambda: schedule_invalidate_project_config(\n                public_key=instance.public_key, trigger=\"projectkey.post_delete\"\n            )\n        )\n\n\n@region_silo_model\nclass ProjectKey(Model):\n    __include_in_export__ = True\n\n    project = FlexibleForeignKey(\"sentry.Project\", related_name=\"key_set\")\n    label = models.CharField(max_length=64, blank=True, null=True)\n    public_key = models.CharField(max_length=32, unique=True, null=True)\n    secret_key = models.CharField(max_length=32, unique=True, null=True)\n    roles = BitField(\n        flags=(\n            # access to post events to the store endpoint\n            (\"store\", \"Event API access\"),\n            # read/write access to rest API\n            (\"api\", \"Web API access\"),\n        ),\n        default=[\"store\"],\n    )\n    status = BoundedPositiveIntegerField(\n        default=0,\n        choices=(\n            (ProjectKeyStatus.ACTIVE, _(\"Active\")),\n            (ProjectKeyStatus.INACTIVE, _(\"Inactive\")),\n        ),\n        db_index=True,\n    )\n    date_added = models.DateTimeField(default=timezone.now, null=True)\n\n    rate_limit_count = BoundedPositiveIntegerField(null=True)\n    rate_limit_window = BoundedPositiveIntegerField(null=True)\n\n    objects = ProjectKeyManager(\n        cache_fields=(\"public_key\", \"secret_key\"),\n        # store projectkeys in memcached for longer than other models,\n        # specifically to make the relay_projectconfig endpoint faster.\n        cache_ttl=60 * 30,\n    )\n\n    data = JSONField()\n\n    # support legacy project keys in API\n    scopes = (\n        \"project:read\",\n        \"project:write\",\n        \"project:admin\",\n        \"project:releases\",\n        \"event:read\",\n        \"event:write\",\n        \"event:admin\",\n    )\n\n    class Meta:\n        app_label = \"sentry\"\n        db_table = \"sentry_projectkey\"\n\n    __repr__ = sane_repr(\"project_id\", \"public_key\")\n\n    def __str__(self):\n        return str(self.public_key)\n\n    @classmethod\n    def generate_api_key(cls):\n        return uuid4().hex\n\n    @classmethod\n    def looks_like_api_key(cls, key):\n        return bool(_uuid4_re.match(key))\n\n    @classmethod\n    def from_dsn(cls, dsn):\n        urlparts = urlparse(dsn)\n\n        public_key = urlparts.username\n        project_id = urlparts.path.rsplit(\"/\", 1)[-1]\n\n        try:\n            return ProjectKey.objects.get(public_key=public_key, project=project_id)\n        except ValueError:\n            # ValueError would come from a non-integer project_id,\n            # which is obviously a DoesNotExist. We catch and rethrow this\n            # so anything downstream expecting DoesNotExist works fine\n            raise ProjectKey.DoesNotExist(\"ProjectKey matching query does not exist.\")\n\n    @classmethod\n    def get_default(cls, project):\n        return cls.objects.filter(\n            project=project,\n            roles=models.F(\"roles\").bitor(cls.roles.store),\n            status=ProjectKeyStatus.ACTIVE,\n        ).first()\n\n    @property\n    def is_active(self):\n        return self.status == ProjectKeyStatus.ACTIVE\n\n    @property\n    def rate_limit(self):\n        if self.rate_limit_count and self.rate_limit_window:\n            return (self.rate_limit_count, self.rate_limit_window)\n        return (0, 0)\n\n    def save(self, *args, **kwargs):\n        if not self.public_key:\n            self.public_key = ProjectKey.generate_api_key()\n        if not self.secret_key:\n            self.secret_key = ProjectKey.generate_api_key()\n        if not self.label:\n            self.label = petname.Generate(2, \" \", letters=10).title()\n        super().save(*args, **kwargs)\n\n    def get_dsn(self, domain=None, secure=True, public=False):\n        urlparts = urlparse(self.get_endpoint(public=public))\n\n        if not public:\n            key = f\"{self.public_key}:{self.secret_key}\"\n        else:\n            key = self.public_key\n\n        # If we do not have a scheme or domain/hostname, dsn is never valid\n        if not urlparts.netloc or not urlparts.scheme:\n            return \"\"\n\n        return \"{}://{}@{}/{}\".format(\n            urlparts.scheme,\n            key,\n            urlparts.netloc + urlparts.path,\n            self.project_id,\n        )\n\n    @property\n    def organization_id(self):\n        return self.project.organization_id\n\n    @property\n    def organization(self):\n        return self.project.organization\n\n    @property\n    def dsn_private(self):\n        return self.get_dsn(public=False)\n\n    @property\n    def dsn_public(self):\n        return self.get_dsn(public=True)\n\n    @property\n    def csp_endpoint(self):\n        endpoint = self.get_endpoint()\n\n        return f\"{endpoint}/api/{self.project_id}/csp-report/?sentry_key={self.public_key}\"\n\n    @property\n    def security_endpoint(self):\n        endpoint = self.get_endpoint()\n\n        return f\"{endpoint}/api/{self.project_id}/security/?sentry_key={self.public_key}\"\n\n    @property\n    def minidump_endpoint(self):\n        endpoint = self.get_endpoint()\n\n        return f\"{endpoint}/api/{self.project_id}/minidump/?sentry_key={self.public_key}\"\n\n    @property\n    def unreal_endpoint(self):\n        return f\"{self.get_endpoint()}/api/{self.project_id}/unreal/{self.public_key}/\"\n\n    @property\n    def js_sdk_loader_cdn_url(self):\n        if settings.JS_SDK_LOADER_CDN_URL:\n            return f\"{settings.JS_SDK_LOADER_CDN_URL}{self.public_key}.min.js\"\n        else:\n            endpoint = self.get_endpoint()\n            return \"{}{}\".format(\n                endpoint,\n                reverse(\"sentry-js-sdk-loader\", args=[self.public_key, \".min\"]),\n            )\n\n    def get_endpoint(self, public=True):\n        if public:\n            endpoint = settings.SENTRY_PUBLIC_ENDPOINT or settings.SENTRY_ENDPOINT\n        else:\n            endpoint = settings.SENTRY_ENDPOINT\n\n        if not endpoint:\n            endpoint = options.get(\"system.url-prefix\")\n\n        has_org_subdomain = False\n        try:\n            has_org_subdomain = features.has(\n                \"organizations:org-subdomains\", self.project.organization\n            )\n        except ProgrammingError:\n            # This happens during migration generation for the organization model.\n            pass\n\n        if has_org_subdomain:\n            urlparts = urlparse(endpoint)\n            if urlparts.scheme and urlparts.netloc:\n                endpoint = \"{}://{}.{}{}\".format(\n                    urlparts.scheme,\n                    settings.SENTRY_ORG_SUBDOMAIN_TEMPLATE.format(\n                        organization_id=self.project.organization_id\n                    ),\n                    urlparts.netloc,\n                    urlparts.path,\n                )\n\n        return endpoint\n\n    def get_allowed_origins(self):\n        from sentry.utils.http import get_origins\n\n        return get_origins(self.project)\n\n    def get_audit_log_data(self):\n        return {\n            \"label\": self.label,\n            \"public_key\": self.public_key,\n            \"secret_key\": self.secret_key,\n            \"roles\": int(self.roles),\n            \"status\": self.status,\n            \"rate_limit_count\": self.rate_limit_count,\n            \"rate_limit_window\": self.rate_limit_window,\n        }\n\n    def get_scopes(self):\n        return self.scopes\n", "path": "src/sentry/models/projectkey.py"}]}