Skip to content

Commit

Permalink
Change Customer.subscriber to a ForeignKey instead of a OneToOneField
Browse files Browse the repository at this point in the history
This is in preparation of adding support for live+test mode customers.
  • Loading branch information
jleclanche committed Mar 8, 2017
1 parent a0b50f0 commit 90007f3
Show file tree
Hide file tree
Showing 6 changed files with 32 additions and 24 deletions.
2 changes: 2 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions djstripe/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
9 changes: 2 additions & 7 deletions djstripe/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
9 changes: 6 additions & 3 deletions tests/test_contrib/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)

Expand Down
5 changes: 2 additions & 3 deletions tests/test_customer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
25 changes: 16 additions & 9 deletions tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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"])

Expand All @@ -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"])

Expand Down Expand Up @@ -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"))

Expand Down

0 comments on commit 90007f3

Please sign in to comment.