From f1d1ee38aaea04ad62a527f1eaccb63ca35944eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Hol=C3=BD?= Date: Sun, 31 Mar 2024 10:58:53 +0200 Subject: [PATCH] replace AbstractRecurringUserPlan.has_automatic_renewal with AbstractRecurringUserPlan.renewal_triggered_by --- CHANGELOG | 12 + .../sample_plans/migrations/0001_initial.py | 13 +- docs/source/plans_recurrence.rst | 8 +- plans/admin.py | 15 +- plans/base/models.py | 90 ++++++- ...userplan_has_automatic_renewal_and_more.py | 39 +++ ...ckup_deprecated_to_renewal_triggered_by.py | 88 ++++++ plans/tasks.py | 3 +- plans/tests/test_autorenew_accounts.py | 83 +++--- plans/tests/tests.py | 255 ++++++++++++++++-- 10 files changed, 542 insertions(+), 64 deletions(-) create mode 100644 plans/migrations/0013_alter_recurringuserplan_has_automatic_renewal_and_more.py create mode 100644 plans/migrations/0014_recurringuserplan_has_automatic_renewal_backup_deprecated_to_renewal_triggered_by.py diff --git a/CHANGELOG b/CHANGELOG index 57f710e6..c8bb1c7c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,18 @@ django-plans changelog ====================== +1.1.0 (unreleased) +------------------ +* Add `AbstractRecurringUserPlan.renewal_triggered_by` +* Use `AbstractRecurringUserPlan.renewal_triggered_by` instead of `has_automatic_renewal`; `has_automatic_renewal` is not used anywhere anymore; `has_automatic_renewal=True` is automatically migrated to `renewal_triggered_by=TASK` and `has_automatic_renewal=False` to `renewal_triggered_by=USER` +* Rename `AbstractRecurringUserPlan.has_automatic_renewal` to `_has_automatic_renewal_backup_deprecated` so it can be used make your own data migration from the former `has_automatic_renewal` to `renewal_triggered_by` if the default one does not work for you +* Add `AbstractRecurringUserPlan.has_automatic_renewal` property that issues a deprecation warning and uses `renewal_triggered_by` under the hood +* Add `renewal_triggered_by` parameter to `AbstractUserPlan.set_plan_renewal` +* Deprecate `AbstractRecurringUserPlan.has_automatic_renewal`; use `AbstractRecurringUserPlan.renewal_triggered_by` instead +* Deprecate `AbstractRecurringUserPlan._has_automatic_renewal_backup_deprecated`; use `AbstractRecurringUserPlan.renewal_triggered_by` instead +* Deprecate `has_automatic_renewal` parameter of `AbstractUserPlan.set_plan_renewal`; use `renewal_triggered_by` instead +* Deprecate `None` value of `renewal_triggered_by` parameter of `AbstractUserPlan.set_plan_renewal`; use an `AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY` instead + 1.0.7 ------------------ * Add `AbstractOrder.return_order()` diff --git a/demo/example/sample_plans/migrations/0001_initial.py b/demo/example/sample_plans/migrations/0001_initial.py index 1c558c88..222e35f4 100644 --- a/demo/example/sample_plans/migrations/0001_initial.py +++ b/demo/example/sample_plans/migrations/0001_initial.py @@ -335,8 +335,19 @@ class Migration(migrations.Migration): ), ("currency", models.CharField(max_length=3, verbose_name="currency")), ( - "has_automatic_renewal", + "renewal_triggered_by", + models.IntegerField( + choices=[(1, "other"), (2, "user"), (3, "task")], + db_index=True, + default=2, + help_text="The source of the associated plan's renewal (USER = user-initiated renewal, TASK = autorenew_account-task-initiated renewal, OTHER = renewal is triggered using another mechanism).", + verbose_name="renewal triggered by", + ), + ), + ( + "_has_automatic_renewal_backup_deprecated", models.BooleanField( + db_column="has_automatic_renewal", default=False, help_text="Automatic renewal is enabled for associated plan. If False, the plan renewal can be still initiated by user.", verbose_name="has automatic plan renewal", diff --git a/docs/source/plans_recurrence.rst b/docs/source/plans_recurrence.rst index 1c404128..24290c02 100644 --- a/docs/source/plans_recurrence.rst +++ b/docs/source/plans_recurrence.rst @@ -3,13 +3,13 @@ Plans recurrence and automatic renewal To support renewal of plans, use ``RecurringUserPlan`` model to store information about the recurrence. -The plans can be renewed automatically, or the ``RecurringUserPlan`` information can be used only to store information for one-click user initiated renewal (with ``automatic_renewal=False``). +The plans can be renewed automatically using this app, the ``RecurringUserPlan`` information can be used only to store information for one-click user initiated renewal (with ``renewal_triggered_by=USER``), or the ``RecurringUserPlan`` can indicate that another mechanism is used to automatically renew the plans (``renewal_triggered_by=OTHER``). -For plans, that should be renewed automatically fill in information about the recurrence:: +For plans, that should be renewed automatically using this app fill in information about the recurrence:: self.order.user.userplan.set_plan_renewal( order=self.order, - automatic_renewal=True, + renewal_triggered_by=TASK, ... # Not required payment_provider='FooProvider', @@ -19,7 +19,7 @@ For plans, that should be renewed automatically fill in information about the re ... ) -Then all active ``UserPlan`` with ``RecurringUserPlan.has_automatic_renewal=True`` will be picked by ``autorenew_account`` task, that will send ``account_automatic_renewal`` signal. +Then all active ``UserPlan`` with ``RecurringUserPlan.renewal_triggered_by=TASK`` will be picked by ``autorenew_account`` task, that will send ``account_automatic_renewal`` signal. This signal can be used for your implementation of automatic plan renewal. You should implement following steps:: @receiver(account_automatic_renewal) diff --git a/plans/admin.py b/plans/admin.py index 3e88d158..0c492077 100644 --- a/plans/admin.py +++ b/plans/admin.py @@ -261,7 +261,7 @@ class UserPlanAdmin(UserLinkMixin, admin.ModelAdmin): "plan__name", "plan__available", "plan__visible", - "recurring__has_automatic_renewal", + "recurring__renewal_triggered_by", "recurring__payment_provider", "recurring__token_verified", "recurring__pricing", @@ -272,7 +272,7 @@ class UserPlanAdmin(UserLinkMixin, admin.ModelAdmin): "plan", "expire", "active", - "recurring__automatic_renewal", + "recurring__renewal_triggered_by", "recurring__token_verified", "recurring__payment_provider", "recurring__pricing", @@ -290,12 +290,13 @@ class UserPlanAdmin(UserLinkMixin, admin.ModelAdmin): "plan", ] - def recurring__automatic_renewal(self, obj): - return obj.recurring.has_automatic_renewal + def recurring__renewal_triggered_by(self, obj): + return obj.recurring.renewal_triggered_by - recurring__automatic_renewal.admin_order_field = "recurring__has_automatic_renewal" - recurring__automatic_renewal.boolean = True - recurring__automatic_renewal.short_description = "Automatic renewal" + recurring__renewal_triggered_by.admin_order_field = ( + "recurring__renewal_triggered_by" + ) + recurring__renewal_triggered_by.short_description = "Renewal triggered by" def recurring__token_verified(self, obj): return obj.recurring.token_verified diff --git a/plans/base/models.py b/plans/base/models.py index f004eeed..035b7d88 100644 --- a/plans/base/models.py +++ b/plans/base/models.py @@ -2,6 +2,7 @@ import logging import re +import warnings from datetime import date, timedelta from decimal import Decimal @@ -299,7 +300,8 @@ def get_plan_extended_from(self, plan): def has_automatic_renewal(self): return ( hasattr(self, "recurring") - and self.recurring.has_automatic_renewal + and self.recurring.renewal_triggered_by + != self.recurring.RENEWAL_TRIGGERED_BY.USER and self.recurring.token_verified ) @@ -332,13 +334,40 @@ def plan_autorenew_at(self): days=plans_autorenew_before_days, hours=plans_autorenew_before_hours ) - def set_plan_renewal(self, order, has_automatic_renewal=True, **kwargs): + def set_plan_renewal( + self, + order, + # TODO: has_automatic_renewal deprecated. Remove in the next major release. + has_automatic_renewal=None, + # TODO: renewal_triggered_by=None deprecated. Set to TASK in the next major release. + renewal_triggered_by=None, + **kwargs, + ): """ Creates or updates plan renewal information for this userplan with given order """ if not hasattr(self, "recurring"): self.recurring = AbstractRecurringUserPlan.get_concrete_model()() + if has_automatic_renewal is None and renewal_triggered_by is None: + has_automatic_renewal = True + if has_automatic_renewal is not None: + warnings.warn( + "has_automatic_renewal is deprecated. Use renewal_triggered_by instead.", + DeprecationWarning, + ) + if renewal_triggered_by is None: + warnings.warn( + "renewal_triggered_by=None is deprecated. " + "Set an AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY instead.", + DeprecationWarning, + ) + renewal_triggered_by = ( + self.recurring.RENEWAL_TRIGGERED_BY.TASK + if has_automatic_renewal + else self.recurring.RENEWAL_TRIGGERED_BY.USER + ) + # Erase values of all fields # We don't want to mix the old and new values self.recurring.set_all_fields_default() @@ -349,7 +378,7 @@ def set_plan_renewal(self, order, has_automatic_renewal=True, **kwargs): self.recurring.amount = order.amount self.recurring.tax = order.tax self.recurring.currency = order.currency - self.recurring.has_automatic_renewal = has_automatic_renewal + self.recurring.renewal_triggered_by = renewal_triggered_by for k, v in kwargs.items(): setattr(self.recurring, k, v) self.recurring.save() @@ -509,6 +538,14 @@ class AbstractRecurringUserPlan(BaseMixin, models.Model): More about recurring payments in docs. """ + RENEWAL_TRIGGERED_BY = Enumeration( + [ + (1, "OTHER", pgettext_lazy("Renewal triggered by", "other")), + (2, "USER", pgettext_lazy("Renewal triggered by", "user")), + (3, "TASK", pgettext_lazy("Renewal triggered by", "task")), + ] + ) + user_plan = models.OneToOneField( "UserPlan", on_delete=models.CASCADE, related_name="recurring" ) @@ -550,12 +587,26 @@ class AbstractRecurringUserPlan(BaseMixin, models.Model): _("tax"), max_digits=4, decimal_places=2, db_index=True, null=True, blank=True ) # Tax=None is when tax is not applicable currency = models.CharField(_("currency"), max_length=3) - has_automatic_renewal = models.BooleanField( + renewal_triggered_by = models.IntegerField( + _("renewal triggered by"), + choices=RENEWAL_TRIGGERED_BY, + help_text=_( + "The source of the associated plan's renewal (USER = user-initiated renewal, " + "TASK = autorenew_account-task-initiated renewal, OTHER = renewal is triggered using another mechanism)." + ), + default=RENEWAL_TRIGGERED_BY.USER, + db_index=True, + ) + # A backup of the old has_automatic_renewal field to support data migration to the new renewal_triggered_by field. + # Do not make any other modifications to the field in order to let user's auto-migrations detect the renaming. + # TODO: _has_automatic_renewal_backup_deprecated deprecated. Remove in the next major release. + _has_automatic_renewal_backup_deprecated = models.BooleanField( _("has automatic plan renewal"), help_text=_( "Automatic renewal is enabled for associated plan. " "If False, the plan renewal can be still initiated by user.", ), + db_column="has_automatic_renewal", default=False, ) token_verified = models.BooleanField( @@ -572,6 +623,35 @@ class AbstractRecurringUserPlan(BaseMixin, models.Model): class Meta: abstract = True + # TODO: has_automatic_renewal deprecated. Remove in the next major release. + @property + def has_automatic_renewal(self): + warnings.warn( + "has_automatic_renewal is deprecated. Use renewal_triggered_by instead.", + DeprecationWarning, + ) + return self.renewal_triggered_by != self.RENEWAL_TRIGGERED_BY.USER + + # TODO: has_automatic_renewal deprecated. Remove in the next major release. + @has_automatic_renewal.setter + def has_automatic_renewal(self, value): + warnings.warn( + "has_automatic_renewal is deprecated. Use renewal_triggered_by instead.", + DeprecationWarning, + ) + self.renewal_triggered_by = ( + self.RENEWAL_TRIGGERED_BY.TASK if value else self.RENEWAL_TRIGGERED_BY.USER + ) + + # TODO: has_automatic_renewal deprecated. Remove in the next major release. + @has_automatic_renewal.deleter + def has_automatic_renewal(self): + warnings.warn( + "has_automatic_renewal is deprecated. Use renewal_triggered_by instead.", + DeprecationWarning, + ) + del self.renewal_triggered_by + def create_renew_order(self): """ Create order for plan renewal @@ -605,7 +685,7 @@ def set_all_fields_default(self): self.amount = None self.tax = None self.currency = None - self.has_automatic_renewal = False + self.renewal_triggered_by = self.RENEWAL_TRIGGERED_BY.USER self.token_verified = False self.card_expire_year = None self.card_expire_month = None diff --git a/plans/migrations/0013_alter_recurringuserplan_has_automatic_renewal_and_more.py b/plans/migrations/0013_alter_recurringuserplan_has_automatic_renewal_and_more.py new file mode 100644 index 00000000..03fa8b5a --- /dev/null +++ b/plans/migrations/0013_alter_recurringuserplan_has_automatic_renewal_and_more.py @@ -0,0 +1,39 @@ +# Generated by Django 4.2.11 on 2024-04-09 10:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("plans", "0012_planpricing_visible"), + ] + + operations = [ + migrations.AlterField( + model_name="recurringuserplan", + name="has_automatic_renewal", + field=models.BooleanField( + db_column="has_automatic_renewal", + default=False, + help_text="Automatic renewal is enabled for associated plan. If False, the plan renewal can be still initiated by user.", + verbose_name="has automatic plan renewal", + ), + ), + migrations.RenameField( + model_name="recurringuserplan", + old_name="has_automatic_renewal", + new_name="_has_automatic_renewal_backup_deprecated", + ), + migrations.AddField( + model_name="recurringuserplan", + name="renewal_triggered_by", + field=models.IntegerField( + choices=[(1, "other"), (2, "user"), (3, "task")], + db_index=True, + default=2, + help_text="The source of the associated plan's renewal (USER = user-initiated renewal, TASK = autorenew_account-task-initiated renewal, OTHER = renewal is triggered using another mechanism).", + verbose_name="renewal triggered by", + ), + ), + ] diff --git a/plans/migrations/0014_recurringuserplan_has_automatic_renewal_backup_deprecated_to_renewal_triggered_by.py b/plans/migrations/0014_recurringuserplan_has_automatic_renewal_backup_deprecated_to_renewal_triggered_by.py new file mode 100644 index 00000000..7cf23062 --- /dev/null +++ b/plans/migrations/0014_recurringuserplan_has_automatic_renewal_backup_deprecated_to_renewal_triggered_by.py @@ -0,0 +1,88 @@ +# Generated by Django 4.2.11 on 2024-04-10 12:50 + +from enum import IntEnum + +from django.db import migrations + + +def _recurringuserplan_has_automatic_renewal_backup_deprecated_to_renewal_triggered_by( + apps, schema_editor +): + RecurringUserPlan = apps.get_model("plans", "RecurringUserPlan") + recurringuserplans_changed = ( + RecurringUserPlan.objects.select_for_update() + .exclude( + _has_automatic_renewal_backup_deprecated=True, + renewal_triggered_by=_RenewalTriggeredByEnum.task, + ) + .exclude( + _has_automatic_renewal_backup_deprecated=False, + renewal_triggered_by=_RenewalTriggeredByEnum.user, + ) + ) + for recurringuserplan_changed in recurringuserplans_changed: + print( + "RecurringUserPlan's renewal_triggered_by will be overwritten:", + recurringuserplan_changed.pk, + ) + RecurringUserPlan.objects.filter( + _has_automatic_renewal_backup_deprecated=True + ).update(renewal_triggered_by=_RenewalTriggeredByEnum.task) + RecurringUserPlan.objects.filter( + _has_automatic_renewal_backup_deprecated=False + ).update(renewal_triggered_by=_RenewalTriggeredByEnum.user) + + +def _recurringuserplan_renewal_triggered_by_to_has_automatic_renewal_backup_deprecated( + apps, schema_editor +): + RecurringUserPlan = apps.get_model("plans", "RecurringUserPlan") + recurringuserplans_changed = ( + RecurringUserPlan.objects.select_for_update() + .exclude( + renewal_triggered_by__in={ + _RenewalTriggeredByEnum.task, + _RenewalTriggeredByEnum.other, + }, + _has_automatic_renewal_backup_deprecated=True, + ) + .exclude( + renewal_triggered_by=_RenewalTriggeredByEnum.user, + _has_automatic_renewal_backup_deprecated=False, + ) + ) + for recurringuserplan_changed in recurringuserplans_changed: + print( + "RecurringUserPlan's _has_automatic_renewal_backup_deprecated will be overwritten:", + recurringuserplan_changed.pk, + ) + RecurringUserPlan.objects.filter( + renewal_triggered_by__in={ + _RenewalTriggeredByEnum.task, + _RenewalTriggeredByEnum.other, + } + ).update(_has_automatic_renewal_backup_deprecated=True) + RecurringUserPlan.objects.filter( + renewal_triggered_by=_RenewalTriggeredByEnum.user + ).update(_has_automatic_renewal_backup_deprecated=False) + RecurringUserPlan.objects.update(renewal_triggered_by=_RenewalTriggeredByEnum.user) + + +class _RenewalTriggeredByEnum(IntEnum): + other = 1 + user = 2 + task = 3 + + +class Migration(migrations.Migration): + + dependencies = [ + ("plans", "0013_alter_recurringuserplan_has_automatic_renewal_and_more"), + ] + + operations = [ + migrations.RunPython( + _recurringuserplan_has_automatic_renewal_backup_deprecated_to_renewal_triggered_by, + reverse_code=_recurringuserplan_renewal_triggered_by_to_has_automatic_renewal_backup_deprecated, + ) + ] diff --git a/plans/tasks.py b/plans/tasks.py index 7dfe8950..d3d6fa65 100644 --- a/plans/tasks.py +++ b/plans/tasks.py @@ -4,6 +4,7 @@ from django.conf import settings from django.contrib.auth import get_user_model +from .base.models import AbstractRecurringUserPlan from .signals import account_automatic_renewal User = get_user_model() @@ -24,7 +25,7 @@ def autorenew_account(providers=None): PLANS_AUTORENEW_BEFORE_HOURS = getattr(settings, "PLANS_AUTORENEW_BEFORE_HOURS", 0) accounts_for_renewal = get_active_plans().filter( - userplan__recurring__has_automatic_renewal=True, + userplan__recurring__renewal_triggered_by=AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.TASK, userplan__recurring__token_verified=True, userplan__expire__lt=datetime.date.today() + datetime.timedelta( diff --git a/plans/tests/test_autorenew_accounts.py b/plans/tests/test_autorenew_accounts.py index 53dbdfa9..22945a9d 100644 --- a/plans/tests/test_autorenew_accounts.py +++ b/plans/tests/test_autorenew_accounts.py @@ -6,6 +6,8 @@ from django.test import TestCase from model_bakery import baker +from plans.models import AbstractRecurringUserPlan + class ManagementCommandTests(TestCase): def test_command_output(self): @@ -14,21 +16,10 @@ def test_command_output(self): self.assertEqual(out.getvalue(), "Starting renewal\nNo accounts autorenewed\n") def test_renewal(self): - self.user = baker.make("User", username="testuser") - plan_pricing = baker.make( - "PlanPricing", plan=baker.make("Plan", name="Foo plan") - ) - baker.make( - "UserPlan", - user=self.user, - plan=plan_pricing.plan, - recurring__payment_provider="internal-payment-recurring", - recurring__amount=Decimal(123), - recurring__pricing=plan_pricing.pricing, - recurring__currency="USD", - recurring__has_automatic_renewal=True, - recurring__token_verified=True, - expire=datetime.date(2020, 1, 2), + _make_user( + userplan__expire=datetime.date(2020, 1, 2), + userplan__recurring__renewal_triggered_by=AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.TASK, + userplan__recurring__token_verified=True, ) out = StringIO() call_command("autorenew_accounts", stdout=out) @@ -37,23 +28,55 @@ def test_renewal(self): "Starting renewal\nAccounts submitted to renewal:\n\tinternal-payment-recurring\t\ttestuser", ) - def test_renewal_providers(self): - self.user = baker.make("User", username="testuser") - plan_pricing = baker.make( - "PlanPricing", plan=baker.make("Plan", name="Foo plan") + def test_renewal_triggered_by_user(self): + _make_user( + userplan__expire=datetime.date(2020, 1, 2), + userplan__recurring__renewal_triggered_by=AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.USER, + userplan__recurring__token_verified=True, ) - baker.make( - "UserPlan", - user=self.user, - plan=plan_pricing.plan, - recurring__payment_provider="internal-payment-recurring", - recurring__amount=Decimal(123), - recurring__pricing=plan_pricing.pricing, - recurring__currency="USD", - recurring__has_automatic_renewal=True, - recurring__token_verified=True, - expire=datetime.date(2020, 1, 2), + out = StringIO() + call_command("autorenew_accounts", stdout=out) + self.assertEqual(out.getvalue(), "Starting renewal\nNo accounts autorenewed\n") + + def test_renewal_triggered_by_other(self): + _make_user( + userplan__expire=datetime.date(2020, 1, 2), + userplan__recurring__renewal_triggered_by=AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.OTHER, + userplan__recurring__token_verified=True, + ) + out = StringIO() + call_command("autorenew_accounts", stdout=out) + self.assertEqual(out.getvalue(), "Starting renewal\nNo accounts autorenewed\n") + + def test_renewal_providers(self): + _make_user( + userplan__expire=datetime.date(2020, 1, 2), + userplan__recurring__renewal_triggered_by=AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.TASK, + userplan__recurring__payment_provider="internal-payment-recurring", + userplan__recurring__token_verified=True, ) out = StringIO() call_command("autorenew_accounts", providers="foo", stdout=out) self.assertEqual(out.getvalue(), "Starting renewal\nNo accounts autorenewed\n") + + +def _make_user( + userplan__expire, + userplan__recurring__renewal_triggered_by, + userplan__recurring__token_verified, + userplan__recurring__payment_provider="internal-payment-recurring", +): + user = baker.make("User", username="testuser") + plan_pricing = baker.make("PlanPricing", plan=baker.make("Plan", name="Foo plan")) + baker.make( + "UserPlan", + user=user, + plan=plan_pricing.plan, + recurring__payment_provider=userplan__recurring__payment_provider, + recurring__amount=Decimal(123), + recurring__pricing=plan_pricing.pricing, + recurring__currency="USD", + recurring__renewal_triggered_by=userplan__recurring__renewal_triggered_by, + recurring__token_verified=userplan__recurring__token_verified, + expire=userplan__expire, + ) diff --git a/plans/tests/tests.py b/plans/tests/tests.py index 11212298..24b45561 100644 --- a/plans/tests/tests.py +++ b/plans/tests/tests.py @@ -1,5 +1,6 @@ import random import re +import warnings from datetime import date, datetime, time, timedelta from decimal import Decimal from io import StringIO @@ -31,6 +32,7 @@ AbstractPlan, AbstractPlanPricing, AbstractPricing, + AbstractRecurringUserPlan, AbstractUserPlan, ) from plans.plan_change import PlanChangePolicy, StandardPlanChangePolicy @@ -1490,16 +1492,138 @@ def test_set_plan_renewal(self): """Test that UserPlan.set_plan_renewal() method""" up = baker.make("UserPlan") o = baker.make("Order", amount=10) - up.set_plan_renewal(order=o, card_masked_number="1234") - self.assertEqual(up.recurring.amount, 10) - self.assertEqual(up.recurring.card_masked_number, "1234") - old_id = up.recurring.id - # test setting new values - up.set_plan_renewal(order=o) - self.assertEqual(up.recurring.amount, 10) - self.assertEqual(up.recurring.card_masked_number, None) - self.assertEqual(up.recurring.id, old_id) + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + + up.set_plan_renewal( + order=o, + renewal_triggered_by=AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.TASK, + card_masked_number="1234", + ) + self.assertEqual(up.recurring.amount, 10) + self.assertEqual(up.recurring.card_masked_number, "1234") + self.assertFalse(caught_warnings) + old_id = up.recurring.id + + # test setting new values + up.set_plan_renewal( + order=o, + renewal_triggered_by=AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.TASK, + ) + self.assertEqual(up.recurring.amount, 10) + self.assertEqual(up.recurring.card_masked_number, None) + self.assertEqual(up.recurring.id, old_id) + self.assertFalse(caught_warnings) + + # test renewal_triggered_by overrides has_automatic_renewal + up.set_plan_renewal( + order=o, + renewal_triggered_by=AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.TASK, + has_automatic_renewal=False, + ) + self.assertEqual( + up.recurring.renewal_triggered_by, + AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.TASK, + ) + self.assertEqual(len(caught_warnings), 1) + caught_warning = caught_warnings.pop() + self.assertTrue(issubclass(caught_warning.category, DeprecationWarning)) + self.assertEqual( + str(caught_warning.message), + "has_automatic_renewal is deprecated. Use renewal_triggered_by instead.", + ) + up.set_plan_renewal( + order=o, + renewal_triggered_by=AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.USER, + has_automatic_renewal=True, + ) + self.assertEqual( + up.recurring.renewal_triggered_by, + AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.USER, + ) + self.assertEqual(len(caught_warnings), 1) + caught_warning = caught_warnings.pop() + self.assertTrue(issubclass(caught_warning.category, DeprecationWarning)) + self.assertEqual( + str(caught_warning.message), + "has_automatic_renewal is deprecated. Use renewal_triggered_by instead.", + ) + up.set_plan_renewal( + order=o, + renewal_triggered_by=AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.OTHER, + has_automatic_renewal=True, + ) + self.assertEqual( + up.recurring.renewal_triggered_by, + AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.OTHER, + ) + self.assertEqual(len(caught_warnings), 1) + caught_warning = caught_warnings.pop() + self.assertTrue(issubclass(caught_warning.category, DeprecationWarning)) + self.assertEqual( + str(caught_warning.message), + "has_automatic_renewal is deprecated. Use renewal_triggered_by instead.", + ) + + # test deprecated backward compatibility + up.set_plan_renewal(order=o) + self.assertEqual( + up.recurring.renewal_triggered_by, + AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.TASK, + ) + self.assertEqual(len(caught_warnings), 2) + caught_warning = caught_warnings.pop() + self.assertTrue(issubclass(caught_warning.category, DeprecationWarning)) + self.assertEqual( + str(caught_warning.message), + "renewal_triggered_by=None is deprecated. " + "Set an AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY instead.", + ) + caught_warning = caught_warnings.pop() + self.assertTrue(issubclass(caught_warning.category, DeprecationWarning)) + self.assertEqual( + str(caught_warning.message), + "has_automatic_renewal is deprecated. Use renewal_triggered_by instead.", + ) + up.set_plan_renewal(order=o, has_automatic_renewal=True) + self.assertEqual( + up.recurring.renewal_triggered_by, + AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.TASK, + ) + self.assertEqual(len(caught_warnings), 2) + caught_warning = caught_warnings.pop() + self.assertTrue(issubclass(caught_warning.category, DeprecationWarning)) + self.assertEqual( + str(caught_warning.message), + "renewal_triggered_by=None is deprecated. " + "Set an AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY instead.", + ) + caught_warning = caught_warnings.pop() + self.assertTrue(issubclass(caught_warning.category, DeprecationWarning)) + self.assertEqual( + str(caught_warning.message), + "has_automatic_renewal is deprecated. Use renewal_triggered_by instead.", + ) + up.set_plan_renewal(order=o, has_automatic_renewal=False) + self.assertEqual( + up.recurring.renewal_triggered_by, + AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.USER, + ) + self.assertEqual(len(caught_warnings), 2) + caught_warning = caught_warnings.pop() + self.assertTrue(issubclass(caught_warning.category, DeprecationWarning)) + self.assertEqual( + str(caught_warning.message), + "renewal_triggered_by=None is deprecated. " + "Set an AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY instead.", + ) + caught_warning = caught_warnings.pop() + self.assertTrue(issubclass(caught_warning.category, DeprecationWarning)) + self.assertEqual( + str(caught_warning.message), + "has_automatic_renewal is deprecated. Use renewal_triggered_by instead.", + ) def test_plan_autorenew_at(self): """Test that UserPlan.plan_autorenew_at() method""" @@ -1520,15 +1644,25 @@ def test_plan_autorenew_at_settings(self): up = baker.make("UserPlan", expire=date(2020, 1, 5)) self.assertEqual(up.plan_autorenew_at(), date(2020, 1, 1)) - def test_has_automatic_renewal(self): + def test_userplan_has_automatic_renewal(self): """Test UserPlan.has_automatic_renewal() method""" user_plan = baker.make("UserPlan") order = baker.make("Order", amount=10) - user_plan.set_plan_renewal(order=order, card_masked_number="1234") - self.assertEqual(user_plan.has_automatic_renewal(), False) - user_plan.recurring.token_verified = True - self.assertEqual(user_plan.has_automatic_renewal(), True) + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + + user_plan.set_plan_renewal( + order=order, + renewal_triggered_by=AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.TASK, + card_masked_number="1234", + ) + self.assertEqual(user_plan.has_automatic_renewal(), False) + self.assertFalse(caught_warnings) + + user_plan.recurring.token_verified = True + self.assertEqual(user_plan.has_automatic_renewal(), True) + self.assertFalse(caught_warnings) def test_create_new_order(self): rup = baker.make( @@ -1553,6 +1687,87 @@ def test_create_new_order_VIES_fault(self): order = rup.create_renew_order() self.assertEqual(order.tax, 11) + def test_has_automatic_renewal(self): + rup = baker.make("RecurringUserPlan") + + with warnings.catch_warnings(record=True) as caught_warnings: + warnings.simplefilter("always") + + rup.renewal_triggered_by = ( + AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.USER + ) + self.assertFalse(rup.has_automatic_renewal) + self.assertEqual(len(caught_warnings), 1) + caught_warning = caught_warnings.pop() + self.assertTrue(issubclass(caught_warning.category, DeprecationWarning)) + self.assertEqual( + str(caught_warning.message), + "has_automatic_renewal is deprecated. Use renewal_triggered_by instead.", + ) + + rup.renewal_triggered_by = ( + AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.TASK + ) + self.assertTrue(rup.has_automatic_renewal) + self.assertEqual(len(caught_warnings), 1) + caught_warning = caught_warnings.pop() + self.assertTrue(issubclass(caught_warning.category, DeprecationWarning)) + self.assertEqual( + str(caught_warning.message), + "has_automatic_renewal is deprecated. Use renewal_triggered_by instead.", + ) + + rup.renewal_triggered_by = ( + AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.OTHER + ) + self.assertTrue(rup.has_automatic_renewal) + self.assertEqual(len(caught_warnings), 1) + caught_warning = caught_warnings.pop() + self.assertTrue(issubclass(caught_warning.category, DeprecationWarning)) + self.assertEqual( + str(caught_warning.message), + "has_automatic_renewal is deprecated. Use renewal_triggered_by instead.", + ) + + rup.has_automatic_renewal = False + self.assertEqual( + rup.renewal_triggered_by, + AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.USER, + ) + self.assertEqual(len(caught_warnings), 1) + caught_warning = caught_warnings.pop() + self.assertTrue(issubclass(caught_warning.category, DeprecationWarning)) + self.assertEqual( + str(caught_warning.message), + "has_automatic_renewal is deprecated. Use renewal_triggered_by instead.", + ) + + rup.has_automatic_renewal = True + self.assertEqual( + rup.renewal_triggered_by, + AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.TASK, + ) + self.assertEqual(len(caught_warnings), 1) + caught_warning = caught_warnings.pop() + self.assertTrue(issubclass(caught_warning.category, DeprecationWarning)) + self.assertEqual( + str(caught_warning.message), + "has_automatic_renewal is deprecated. Use renewal_triggered_by instead.", + ) + + del rup.has_automatic_renewal + self.assertEqual( + rup.renewal_triggered_by, + AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.USER, + ) + self.assertEqual(len(caught_warnings), 1) + caught_warning = caught_warnings.pop() + self.assertTrue(issubclass(caught_warning.category, DeprecationWarning)) + self.assertEqual( + str(caught_warning.message), + "has_automatic_renewal is deprecated. Use renewal_triggered_by instead.", + ) + class TasksTestCase(TestCase): def setUp(self): @@ -1565,7 +1780,11 @@ def test_expire_account_task(self): userplan.active = True # If the automatic renewal didn't go through, even automatic renewal plans has to go - userplan.set_plan_renewal(order=order, card_masked_number="1234") + userplan.set_plan_renewal( + order=order, + renewal_triggered_by=AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.TASK, + card_masked_number="1234", + ) userplan.save() tasks.expire_account() @@ -1585,7 +1804,11 @@ def test_expire_account_task_notify(self): userplan.active = True # If the automatic renewal didn't go through, even automatic renewal plans has to go - userplan.set_plan_renewal(order=order, card_masked_number="1234") + userplan.set_plan_renewal( + order=order, + renewal_triggered_by=AbstractRecurringUserPlan.RENEWAL_TRIGGERED_BY.TASK, + card_masked_number="1234", + ) userplan.save() tasks.expire_account()