Repository: dj-stripe/dj-stripe

Issue:
Drop django.contrib.sites as a dependency
When djstripe attempts to send an email for invoices, it assumes that the site is using the Site middleware. I happened to have that disabled, and it raises an exception.



Discussion:
We use the sites framework to grab the site name and domain for email templates. You won't need to run the middleware for email sending to work, but you will need to [enable the framework](https://docs.djangoproject.com/en/1.8/ref/contrib/sites/#enabling-the-sites-framework). I'll be sure to make a note of that in the docs.

Just to double check, is this the exception raised?

``` python
ImproperlyConfigured(
    "You're using the Django \"sites framework\" without having "
    "set the SITE_ID setting. Create a site in your database and "
    "set the SITE_ID setting or pass a request to "
    "Site.objects.get_current() to fix this error."
)
```

I suppose we don't need to have the Sites framework as a dependency, especially since it's only used in that one spot. I like the idea of getting rid of dependencies, but in this case, there isn't a better solution for getting those site variables into the templates.

Maybe a setting like `DJSTRIPE_USE_STATIC_SITE_VARIABLES(=False)` would work. If True, it would leverage `DJSTRIPE_SITE_NAME` and `DJSTRIPE_SITE_DOMAIN`; if False, it would leverage the sites framework. That's not very elegant though...

I'll have to give this a bit of thought. If anyone comes across this and has an opinion, feel free to discuss here.

As OP, I don't really have much of an opinion. I have special requirements for sending emails, so I disable the emailing feature anyways. Just something I ran across.

Okay. Were you able to get the setting to work?

Yes I was. I'll comment on that ticket. It was entirely PEBKAC.

Should we mark this issue as closed?

@pydanny: Up to you. I kept it open as a reminder to possibly implement my comment above. Might be nice to drop the dependency, but I can go either way.

@pydanny: I forgot I had this referenced in another issue. Closing.


Resolving patch:
diff --git a/djstripe/models.py b/djstripe/models.py
--- a/djstripe/models.py
+++ b/djstripe/models.py
@@ -16,17 +16,13 @@
 import sys
 from datetime import timedelta
 
-from django.conf import settings
-from django.contrib.sites.models import Site
 from django.core.exceptions import ValidationError
-from django.core.mail import EmailMessage
 from django.db import models
 from django.db.models.fields import (
     BooleanField, CharField, DateTimeField, NullBooleanField, TextField, UUIDField
 )
 from django.db.models.fields.related import ForeignKey, OneToOneField
 from django.db.models.deletion import SET_NULL
-from django.template.loader import render_to_string
 from django.utils import six, timezone
 from django.utils.encoding import python_2_unicode_compatible, smart_text
 from django.utils.functional import cached_property
@@ -96,29 +92,6 @@ def capture(self):
         captured_charge = super(Charge, self).capture()
         return Charge.sync_from_stripe_data(captured_charge)
 
-    def send_receipt(self):
-        """Send a receipt for this charge."""
-
-        if not self.receipt_sent:
-            site = Site.objects.get_current()
-            protocol = getattr(settings, "DEFAULT_HTTP_PROTOCOL", "http")
-            ctx = {
-                "charge": self,
-                "site": site,
-                "protocol": protocol,
-            }
-            subject = render_to_string("djstripe/email/subject.txt", ctx)
-            subject = subject.strip()
-            message = render_to_string("djstripe/email/body.txt", ctx)
-            num_sent = EmailMessage(
-                subject,
-                message,
-                to=[self.customer.subscriber.email],
-                from_email=djstripe_settings.INVOICE_FROM_EMAIL
-            ).send()
-            self.receipt_sent = num_sent > 0
-            self.save()
-
     def _attach_objects_hook(self, cls, data):
         customer = cls._stripe_object_to_customer(target_cls=Customer, data=data)
         if customer:
@@ -338,16 +311,10 @@ def can_charge(self):
 
         return self.has_valid_source() and self.date_purged is None
 
-    def charge(self, amount, currency="usd", send_receipt=None, **kwargs):
-        if send_receipt is None:
-            send_receipt = getattr(settings, 'DJSTRIPE_SEND_INVOICE_RECEIPT_EMAILS', True)
-
+    def charge(self, amount, currency="usd", **kwargs):
         stripe_charge = super(Customer, self).charge(amount=amount, currency=currency, **kwargs)
         charge = Charge.sync_from_stripe_data(stripe_charge)
 
-        if send_receipt:
-            charge.send_receipt()
-
         return charge
 
     def add_invoice_item(self, amount, currency, **kwargs):
@@ -654,9 +621,6 @@ def _attach_objects_hook(self, cls, data):
 
         charge = cls._stripe_object_to_charge(target_cls=Charge, data=data)
         if charge:
-            if djstripe_settings.SEND_INVOICE_RECEIPT_EMAILS:
-                charge.send_receipt()
-
             self.charge = charge
 
         subscription = cls._stripe_object_to_subscription(target_cls=Subscription, data=data)
diff --git a/djstripe/settings.py b/djstripe/settings.py
--- a/djstripe/settings.py
+++ b/djstripe/settings.py
@@ -61,7 +61,6 @@ def _get_idempotency_key(object_type, action, livemode):
 get_idempotency_key = get_callback_function("DJSTRIPE_IDEMPOTENCY_KEY_CALLBACK", _get_idempotency_key)
 
 
-INVOICE_FROM_EMAIL = getattr(settings, "DJSTRIPE_INVOICE_FROM_EMAIL", "billing@example.com")
 PAYMENTS_PLANS = getattr(settings, "DJSTRIPE_PLANS", {})
 PLAN_HIERARCHY = getattr(settings, "DJSTRIPE_PLAN_HIERARCHY", {})
 
@@ -69,8 +68,6 @@ def _get_idempotency_key(object_type, action, livemode):
 PRORATION_POLICY_FOR_UPGRADES = getattr(settings, 'DJSTRIPE_PRORATION_POLICY_FOR_UPGRADES', False)
 CANCELLATION_AT_PERIOD_END = not getattr(settings, 'DJSTRIPE_PRORATION_POLICY', False)
 
-SEND_INVOICE_RECEIPT_EMAILS = getattr(settings, "DJSTRIPE_SEND_INVOICE_RECEIPT_EMAILS", True)
-
 DEFAULT_PLAN = getattr(settings, "DJSTRIPE_DEFAULT_PLAN", None)
 
 # Try to find the new settings variable first. If that fails, revert to the
diff --git a/djstripe/stripe_objects.py b/djstripe/stripe_objects.py
--- a/djstripe/stripe_objects.py
+++ b/djstripe/stripe_objects.py
@@ -772,12 +772,6 @@ def charge(self, amount, currency, application_fee=None, capture=None, descripti
         :type source: string, StripeSource
         :param statement_descriptor: An arbitrary string to be displayed on the customer's credit card statement.
         :type statement_descriptor: string
-        :param send_receipt: Whether or not to send a receipt for this charge. If blank,
-                             ``DJSTRIPE_SEND_INVOICE_RECEIPT_EMAILS`` is used.
-        :type send_receipt: boolean
-
-        .. Notes:
-        .. ``send_receipt`` is only available on ``Customer.charge()``
         """
 
         if not isinstance(amount, decimal.Decimal):


Test patch:
diff --git a/tests/test_charge.py b/tests/test_charge.py
--- a/tests/test_charge.py
+++ b/tests/test_charge.py
@@ -143,11 +143,3 @@ def test_sync_from_stripe_data_with_destination(self, account_retrieve_mock, cha
         account = Account.objects.get(stripe_id=FAKE_ACCOUNT["id"])
 
         self.assertEqual(account, charge.account)
-
-    @patch("djstripe.models.Site.objects.get_current")
-    def test_send_receipt_not_sent(self, get_current_mock):
-        charge = Charge(receipt_sent=True)
-        charge.send_receipt()
-
-        # Assert the condition caused exit
-        self.assertFalse(get_current_mock.called)
diff --git a/tests/test_customer.py b/tests/test_customer.py
--- a/tests/test_customer.py
+++ b/tests/test_customer.py
@@ -575,19 +575,6 @@ def test_has_active_subscription_with_plan_string(self, customer_retrieve_mock,
 
         self.customer.has_active_subscription(plan=plan.stripe_id)
 
-    @patch("djstripe.models.Charge.send_receipt")
-    @patch("djstripe.models.Charge.sync_from_stripe_data")
-    @patch("stripe.Charge.retrieve", return_value=FAKE_CHARGE)
-    @patch("stripe.Charge.create", return_value=FAKE_CHARGE)
-    def test_charge_not_send_receipt(self, charge_create_mock, charge_retrieve_mock, charge_sync_mock,
-                                     send_receipt_mock):
-        self.customer.charge(amount=decimal.Decimal("50.00"), send_receipt=False)
-
-        self.assertFalse(charge_retrieve_mock.called)
-        self.assertTrue(charge_create_mock.called)
-        charge_sync_mock.assert_called_once_with(FAKE_CHARGE)
-        self.assertFalse(send_receipt_mock.called)
-
     @patch("djstripe.models.InvoiceItem.sync_from_stripe_data", return_value="pancakes")
     @patch("stripe.InvoiceItem.create", return_value=deepcopy(FAKE_INVOICEITEM))
     def test_add_invoice_item(self, invoiceitem_create_mock, invoiceitem_sync_mock):
diff --git a/tests/test_email.py b/tests/test_email.py
deleted file mode 100644
--- a/tests/test_email.py
+++ /dev/null
@@ -1,44 +0,0 @@
-"""
-.. module:: dj-stripe.tests.test_email
-   :synopsis: dj-stripe Email Tests.
-
-.. moduleauthor:: Alex Kavanaugh (@kavdev)
-
-"""
-
-from copy import deepcopy
-import decimal
-
-from django.contrib.auth import get_user_model
-from django.core import mail
-from django.test import TestCase
-
-from mock import patch
-
-from djstripe.models import Customer, Account
-
-from tests import FAKE_CHARGE, FAKE_CUSTOMER
-
-
-class EmailReceiptTest(TestCase):
-
-    def setUp(self):
-        self.user = get_user_model().objects.create_user(username="pydanny", email="pydanny@gmail.com")
-        self.customer = Customer.objects.create(subscriber=self.user, stripe_id=FAKE_CUSTOMER["id"], livemode=False)
-        self.account = Account.objects.create()
-
-    @patch("djstripe.models.Account.get_default_account")
-    @patch("stripe.Charge.retrieve")
-    @patch("stripe.Charge.create")
-    def test_email_reciept_renders_amount_properly(self, charge_create_mock, charge_retrieve_mock,
-                                                   default_account_mock):
-        default_account_mock.return_value = self.account
-
-        fake_charge_copy = deepcopy(FAKE_CHARGE)
-        fake_charge_copy.update({"invoice": None})
-
-        charge_create_mock.return_value = fake_charge_copy
-        charge_retrieve_mock.return_value = fake_charge_copy
-
-        self.customer.charge(amount=decimal.Decimal("22.00"))
-        self.assertTrue("$22.00" in mail.outbox[0].body)
diff --git a/tests/test_event_handlers.py b/tests/test_event_handlers.py
--- a/tests/test_event_handlers.py
+++ b/tests/test_event_handlers.py
@@ -249,7 +249,6 @@ def test_customer_bogus_event_type(self, event_retrieve_mock, customer_retrieve_
 
 class TestInvoiceEvents(EventTestCase):
 
-    @patch("djstripe.models.Charge.send_receipt", autospec=True)
     @patch("djstripe.models.Account.get_default_account")
     @patch("stripe.Subscription.retrieve", return_value=deepcopy(FAKE_SUBSCRIPTION))
     @patch("stripe.Customer.retrieve", return_value=deepcopy(FAKE_CUSTOMER))
@@ -258,7 +257,7 @@ class TestInvoiceEvents(EventTestCase):
     @patch("stripe.Event.retrieve")
     def test_invoice_created_no_existing_customer(self, event_retrieve_mock, invoice_retrieve_mock,
                                                   charge_retrieve_mock, customer_retrieve_mock,
-                                                  subscription_retrieve_mock, default_account_mock, send_receipt_mock):
+                                                  subscription_retrieve_mock, default_account_mock):
         default_account_mock.return_value = Account.objects.create()
 
         user = get_user_model().objects.create_user(username="pydanny", email="pydanny@gmail.com")
@@ -278,7 +277,6 @@ def test_invoice_created_no_existing_customer(self, event_retrieve_mock, invoice
         with self.assertRaises(CustomerDoesNotExistLocallyException):
             event.process()
 
-    @patch("djstripe.models.Charge.send_receipt", autospec=True)
     @patch("djstripe.models.Account.get_default_account")
     @patch("stripe.Subscription.retrieve", return_value=deepcopy(FAKE_SUBSCRIPTION))
     @patch("stripe.Customer.retrieve", return_value=deepcopy(FAKE_CUSTOMER))
@@ -286,8 +284,7 @@ def test_invoice_created_no_existing_customer(self, event_retrieve_mock, invoice
     @patch("stripe.Invoice.retrieve")
     @patch("stripe.Event.retrieve")
     def test_invoice_created(self, event_retrieve_mock, invoice_retrieve_mock, charge_retrieve_mock,
-                             customer_retrieve_mock, subscription_retrieve_mock, default_account_mock,
-                             send_receipt_mock):
+                             customer_retrieve_mock, subscription_retrieve_mock, default_account_mock):
         default_account_mock.return_value = Account.objects.create()
 
         user = get_user_model().objects.create_user(username="pydanny", email="pydanny@gmail.com")
@@ -310,14 +307,13 @@ def test_invoice_created(self, event_retrieve_mock, invoice_retrieve_mock, charg
         )
         self.assertEquals(invoice.paid, fake_stripe_event["data"]["object"]["paid"])
 
-    @patch("djstripe.models.Charge.send_receipt", autospec=True)
     @patch("djstripe.models.Account.get_default_account")
     @patch("stripe.Subscription.retrieve", return_value=deepcopy(FAKE_SUBSCRIPTION))
     @patch("stripe.Customer.retrieve", return_value=deepcopy(FAKE_CUSTOMER))
     @patch("stripe.Charge.retrieve", return_value=deepcopy(FAKE_CHARGE))
     @patch("stripe.Invoice.retrieve", return_value=deepcopy(FAKE_INVOICE))
     def test_invoice_deleted(self, invoice_retrieve_mock, charge_retrieve_mock, customer_retrieve_mock,
-                             subscription_retrieve_mock, default_account_mock, send_receipt_mock):
+                             subscription_retrieve_mock, default_account_mock):
         default_account_mock.return_value = Account.objects.create()
 
         user = get_user_model().objects.create_user(username="pydanny", email="pydanny@gmail.com")
diff --git a/tests/test_invoice.py b/tests/test_invoice.py
--- a/tests/test_invoice.py
+++ b/tests/test_invoice.py
@@ -129,21 +129,6 @@ def test_status_closed(self, charge_retrieve_mock, subscription_retrieve_mock, d
 
         self.assertEqual(Invoice.STATUS_CLOSED, invoice.status)
 
-    @patch("djstripe.models.djstripe_settings", autospec=True)
-    @patch("djstripe.models.Charge.send_receipt")
-    @patch("djstripe.models.Account.get_default_account")
-    @patch("stripe.Subscription.retrieve", return_value=deepcopy(FAKE_SUBSCRIPTION))
-    @patch("stripe.Charge.retrieve", return_value=deepcopy(FAKE_CHARGE))
-    def test_sync_send_emails_false(self, charge_retrieve_mock, subscription_retrieve_mock, default_account_mock,
-                                    send_receipt_mock, settings_fake):
-        default_account_mock.return_value = self.account
-        settings_fake.SEND_INVOICE_RECEIPT_EMAILS = False
-
-        invoice_data = deepcopy(FAKE_INVOICE)
-        Invoice.sync_from_stripe_data(invoice_data)
-
-        self.assertFalse(send_receipt_mock.called)
-
     @patch("djstripe.models.Account.get_default_account")
     @patch("stripe.Plan.retrieve", return_value=deepcopy(FAKE_PLAN))
     @patch("stripe.Subscription.retrieve", return_value=deepcopy(FAKE_SUBSCRIPTION))