From 90007f36f9f613e9a350c85b04e3f919dd85c7fc Mon Sep 17 00:00:00 2001 From: Jerome Leclanche Date: Tue, 7 Mar 2017 07:57:20 +0200 Subject: [PATCH] Change Customer.subscriber to a ForeignKey instead of a OneToOneField This is in preparation of adding support for live+test mode customers. --- HISTORY.rst | 2 ++ djstripe/models.py | 6 ++++-- djstripe/signals.py | 9 ++------- tests/test_contrib/test_views.py | 9 ++++++--- tests/test_customer.py | 5 ++--- tests/test_views.py | 25 ++++++++++++++++--------- 6 files changed, 32 insertions(+), 24 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 6b20ad1bda..6d46172975 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -5,6 +5,8 @@ History 1.0.0 (2016-??-??) --------------------- +* BACKWARDS-INCOMPATIBLE: dj-stripe now supports test-mode and live-mode Customer objects concurrently. + As a result, the User.customer One-to-One reverse-relationship is now the User.djstripe_customers RelatedManager. (Thanks @jleclanche) #440 * Charge receipts now take `DJSTRIPE_SEND_INVOICE_RECEIPT_EMAILS` into account (Thanks @r0fls) * Clarified/modified installation documentation (Thanks @pydanny) * Corrected and revised ANONYMOUS_USER_ERROR_MSG (Thanks @pydanny) diff --git a/djstripe/models.py b/djstripe/models.py index e91566cf43..22fdb7cb5b 100644 --- a/djstripe/models.py +++ b/djstripe/models.py @@ -152,8 +152,10 @@ class Customer(StripeCustomer): default_source = ForeignKey(StripeSource, null=True, related_name="customers", on_delete=SET_NULL) - subscriber = OneToOneField(djstripe_settings.get_subscriber_model_string(), null=True, - on_delete=SET_NULL, related_name="customer") + subscriber = ForeignKey( + djstripe_settings.get_subscriber_model_string(), null=True, + on_delete=SET_NULL, related_name="djstripe_customers" + ) date_purged = DateTimeField(null=True, editable=False) def str_parts(self): diff --git a/djstripe/signals.py b/djstripe/signals.py index 3c35d9045f..ff43f0ef34 100644 --- a/djstripe/signals.py +++ b/djstripe/signals.py @@ -11,7 +11,6 @@ Stripe docs for Webhooks: https://stripe.com/docs/webhooks """ -from django.core.exceptions import ObjectDoesNotExist from django.dispatch import Signal, receiver from django.db.models.signals import pre_delete from . import settings as djstripe_settings @@ -86,9 +85,5 @@ @receiver(pre_delete, sender=djstripe_settings.get_subscriber_model_string()) def on_delete_subscriber_purge_customer(instance=None, **kwargs): """ Purge associated customers when the subscriber is deleted. """ - try: - customer = instance.customer - except (AttributeError, ObjectDoesNotExist): - return - - customer.purge() + for customer in instance.djstripe_customers.all(): + customer.purge() diff --git a/tests/test_contrib/test_views.py b/tests/test_contrib/test_views.py index eafc3fb721..35c6dc03ba 100644 --- a/tests/test_contrib/test_views.py +++ b/tests/test_contrib/test_views.py @@ -55,8 +55,9 @@ def test_create_subscription(self, stripe_customer_create_mock, add_card_mock, s } response = self.client.post(self.url, data) self.assertEqual(1, Customer.objects.count()) - add_card_mock.assert_called_once_with(self.user.customer, "cake") - subscribe_mock.assert_called_once_with(self.user.customer, "test0", True) + customer = Customer.objects.get() + add_card_mock.assert_called_once_with(customer, "cake") + subscribe_mock.assert_called_once_with(customer, "test0", True) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data, data) @@ -76,7 +77,9 @@ def test_create_subscription_charge_immediately(self, stripe_customer_create_moc "charge_immediately": False, } response = self.client.post(self.url, data) - subscribe_mock.assert_called_once_with(self.user.customer, "test0", False) + self.assertEqual(1, Customer.objects.count()) + customer = Customer.objects.get() + subscribe_mock.assert_called_once_with(customer, "test0", False) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.data, data) diff --git a/tests/test_customer.py b/tests/test_customer.py index f6810cb85d..dc387173ed 100644 --- a/tests/test_customer.py +++ b/tests/test_customer.py @@ -656,7 +656,6 @@ def test_delete_subscriber_purges_customer(self, customer_retrieve_mock): @patch("stripe.Customer.retrieve") def test_delete_subscriber_without_customer_is_noop(self, customer_retrieve_mock): - customer = self.user.customer - self.user.customer = None self.user.delete() - self.assertIsNone(customer.date_purged) + for customer in self.user.djstripe_customers.all(): + self.assertIsNone(customer.date_purged) diff --git a/tests/test_views.py b/tests/test_views.py index bfb2092889..4087519907 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -88,9 +88,11 @@ def test_get(self, stripe_create_customer_mock): def test_post_new_card(self, stripe_customer_create_mock, add_card_mock, send_invoice_mock, retry_unpaid_invoices_mock): self.client.post(self.url, {"stripe_token": "alpha"}) - add_card_mock.assert_called_once_with(self.user.customer, "alpha") - send_invoice_mock.assert_called_with(self.user.customer) - retry_unpaid_invoices_mock.assert_called_once_with(self.user.customer) + self.assertEqual(1, Customer.objects.count()) + customer = Customer.objects.get() + add_card_mock.assert_called_once_with(customer, "alpha") + send_invoice_mock.assert_called_with(customer) + retry_unpaid_invoices_mock.assert_called_once_with(customer) # Needs to be refactored to use sources @patch("djstripe.models.Customer.retry_unpaid_invoices", autospec=True) @@ -106,9 +108,9 @@ def test_post_change_card(self, add_card_mock, send_invoice_mock, retry_unpaid_i self.client.post(self.url, {"stripe_token": "beta"}) self.assertEqual(1, Customer.objects.count()) - add_card_mock.assert_called_once_with(self.user.customer, "beta") + add_card_mock.assert_called_once_with(customer, "beta") self.assertFalse(send_invoice_mock.called) - retry_unpaid_invoices_mock.assert_called_once_with(self.user.customer) + retry_unpaid_invoices_mock.assert_called_once_with(customer) # Needs to be refactored to use sources @patch("djstripe.models.Customer.add_card", autospec=True) @@ -117,7 +119,9 @@ def test_post_card_error(self, stripe_create_customer_mock, add_card_mock): add_card_mock.side_effect = StripeError("An error occurred while processing your card.") response = self.client.post(self.url, {"stripe_token": "pie"}) - add_card_mock.assert_called_once_with(self.user.customer, "pie") + self.assertEqual(1, Customer.objects.count()) + customer = Customer.objects.get() + add_card_mock.assert_called_once_with(customer, "pie") self.assertIn("stripe_error", response.context) self.assertIn("An error occurred while processing your card.", response.context["stripe_error"]) @@ -128,7 +132,9 @@ def test_post_no_card(self, stripe_create_customer_mock, add_card_mock): add_card_mock.side_effect = StripeError("Invalid source object:") response = self.client.post(self.url) - add_card_mock.assert_called_once_with(self.user.customer, None) + self.assertEqual(1, Customer.objects.count()) + customer = Customer.objects.get() + add_card_mock.assert_called_once_with(customer, None) self.assertIn("stripe_error", response.context) self.assertIn("Invalid source object:", response.context["stripe_error"]) @@ -239,8 +245,9 @@ def test_post_valid(self, add_card_mock, subscribe_mock): response = self.client.post(self.url, {"plan": self.plan.id, "stripe_token": "cake"}) self.assertEqual(1, Customer.objects.count()) - add_card_mock.assert_called_once_with(self.user.customer, "cake") - subscribe_mock.assert_called_once_with(self.user.customer, self.plan) + customer = Customer.objects.get() + add_card_mock.assert_called_once_with(customer, "cake") + subscribe_mock.assert_called_once_with(customer, self.plan) self.assertRedirects(response, reverse("djstripe:history"))