Repository: dj-stripe/dj-stripe

Issue:
Fail to add APIKey via djstripe admin interface
**Describe the bug**
When adding a new API key via the djstripe admin interface the following error occurs:
![admin](https://user-images.githubusercontent.com/809590/153444135-de83f010-9889-42b8-b5ff-853218b93b28.jpg)


**To Reproduce**
Simply add a new API key via https://localhost/admin/djstripe/apikey/add/
Hint: the issue only appears when the associated Djstripe Account does not exist yet!

For testing purposes I dropped the database constraint:
`ALTER TABLE public.djstripe_apikey DROP CONSTRAINT djstripe_apikey_secret_key;`
After that I was able to add the API key but I ended up having two identical versions with different labels though.

**Software versions**
- Dj-Stripe version: 2.7.0-alpha6
- Python version: Python 3.9.10
- Django version: 3.2
- Stripe API version: latest
- Database type and version: PostgreSQL 10.19



Discussion:
@dbartenstein This issue was not happening in the previous release?
> @dbartenstein This issue was not happening in the previous release?

@arnav13081994 haven’t tested that with the previous release. So I cannot really tell when it was introduced. Can you reproduce it?
I will take this up later today. Need to finish some stuff for work first.

Resolving patch:
diff --git a/djstripe/admin.py b/djstripe/admin.py
--- a/djstripe/admin.py
+++ b/djstripe/admin.py
@@ -2,7 +2,7 @@
 Django Administration interface definitions
 """
 import json
-from typing import Dict, Optional
+from typing import Any, Dict, Optional
 from urllib.parse import urljoin
 
 import stripe
@@ -11,6 +11,7 @@
 from django.contrib.admin import helpers
 from django.contrib.admin.utils import display_for_field, display_for_value
 from django.core.management import call_command
+from django.db import IntegrityError, transaction
 from django.urls import reverse
 from jsonfield import JSONField
 from stripe.error import AuthenticationError, InvalidRequestError
@@ -346,6 +347,17 @@ def get_form(self, request, obj=None, **kwargs):
             return APIKeyAdminCreateForm
         return super().get_form(request, obj, **kwargs)
 
+    def save_model(self, request: Any, obj, form: Any, change: Any) -> None:
+        try:
+            # for non-existent Platform Accounts, because of Account._find_owner_account()
+            # it will try to retrieve by api_key, Account.get_or_retrieve_for_api_key().
+            # Account.get_or_retrieve_for_api_key will create this APIKey! This would cause
+            # an IntegrityError as the APIKey gets created before this form gets saved
+            with transaction.atomic():
+                obj.save()
+        except IntegrityError:
+            pass
+
 
 @admin.register(models.BalanceTransaction)
 class BalanceTransactionAdmin(ReadOnlyMixin, StripeModelAdmin):
diff --git a/djstripe/models/api.py b/djstripe/models/api.py
--- a/djstripe/models/api.py
+++ b/djstripe/models/api.py
@@ -3,7 +3,7 @@
 from uuid import uuid4
 
 from django.core.validators import RegexValidator
-from django.db import models
+from django.db import IntegrityError, models, transaction
 from django.forms import ValidationError
 
 from ..enums import APIKeyType
@@ -100,7 +100,14 @@ def refresh_account(self, commit=True):
             Account.sync_from_stripe_data(account_data, api_key=self.secret)
         self.djstripe_owner_account = account
         if commit:
-            self.save()
+            try:
+                # for non-existent accounts, due to djstripe_owner_account search for the
+                # accounts themselves, trigerred by this method, the APIKey gets created before this method
+                # can "commit". This results in an Integrity Error
+                with transaction.atomic():
+                    self.save()
+            except IntegrityError:
+                pass
 
     @property
     def secret_redacted(self) -> str:
diff --git a/djstripe/models/payment_methods.py b/djstripe/models/payment_methods.py
--- a/djstripe/models/payment_methods.py
+++ b/djstripe/models/payment_methods.py
@@ -880,7 +880,6 @@ def __str__(self):
             return f"{enums.PaymentMethodType.humanize(self.type)} for {self.customer}"
         return f"{enums.PaymentMethodType.humanize(self.type)} is not associated with any customer"
 
-
     def get_stripe_dashboard_url(self) -> str:
         if self.customer:
             return self.customer.get_stripe_dashboard_url()


Test patch: