Skip to content

Commit

Permalink
Support free plans.
Browse files Browse the repository at this point in the history
Fixes django-getpaid#24

Plans without pricings will be `plan.is_free: true`. Switching to these
plans causes the userplan have it's expiry cleared.

Since there's no expiry set, the standard plan change process doesn't
make sense, so the plan table is adjusted to present the standard plan
order links rather than plan change links.
  • Loading branch information
brentc authored and Marcin Mazurek committed Dec 5, 2014
1 parent 9a871e7 commit 5f926cc
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 29 deletions.
12 changes: 12 additions & 0 deletions plans/fixtures/test_django-plans_plans.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,18 @@
"order": 5
}
},
{
"pk": 9,
"model": "plans.plan",
"fields": {
"available": true,
"visible": true,
"created": "2014-03-30T10:41:52.428Z",
"description": "Free Test",
"name": "Free",
"order": 6
}
},
{
"pk": 6,
"model": "plans.billinginfo",
Expand Down
23 changes: 20 additions & 3 deletions plans/locale/en/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -370,12 +370,17 @@ msgstr ""
msgid "Hi"
msgstr ""

#: templates/mail/change_plan_body.txt:4
#: templates/mail/change_plan_body.txt:5
#, python-format
msgid "Your current plan is %(plan_name)s and it will expire on %(expire)s. "
msgstr ""

#: templates/mail/change_plan_body.txt:6
#: templates/mail/change_plan_body.txt:7
#, python-format
msgid "Your current plan is %(plan_name)s. "
msgstr ""

#: templates/mail/change_plan_body.txt:10
#: templates/mail/expired_account_body.txt:11
#: templates/mail/extend_account_body.txt:8
#: templates/mail/invoice_created_body.txt:11
Expand Down Expand Up @@ -721,7 +726,19 @@ msgstr ""
msgid "Buy"
msgstr ""

#: templates/plans/plan_table.html:93
#: templates/plans/plan_table.html:90
msgid "Free"
msgstr ""

#: templates/plans/plan_table.html:91
msgid "no expiry"
msgstr ""

#: templates/plans/plan_table.html:96
msgid "Select"
msgstr ""

#: templates/plans/plan_table.html:109
#, python-format
msgid ""
"\n"
Expand Down
18 changes: 12 additions & 6 deletions plans/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ def get_quota_dict(self):
quota_dic[plan_quota.quota.codename] = plan_quota.value
return quota_dic

def is_free(self):
return self.planpricing_set.count() == 0

class BillingInfo(models.Model):
"""
Expand Down Expand Up @@ -191,7 +193,8 @@ def initialize(self):
Set up user plan for first use
"""
if not self.is_active():
if self.expire is None:
# Plans without pricings don't need to expire
if self.expire is None and self.plan.planpricing_set.count():
self.expire = now() + timedelta(
days=getattr(settings, 'PLAN_DEFAULT_GRACE_PERIOD', 30))
self.activate() # this will call self.save()
Expand All @@ -209,6 +212,11 @@ def extend_account(self, plan, pricing):
# Process a plan change request (downgrade or upgrade)
# No account activation or extending at this point
self.plan = plan

if self.expire is not None and not plan.planpricing_set.count():
# Assume no expiry date for plans without pricing.
self.expire = None

self.save()
account_change_plan.send(sender=self, user=self.user)
mail_context = Context({'user': self.user, 'userplan': self, 'plan': plan})
Expand All @@ -221,19 +229,17 @@ def extend_account(self, plan, pricing):
# Processing standard account extending procedure
if self.plan == plan:
status = True
if self.expire is None:
pass
elif self.expire > date.today():
if self.expire is not None and self.expire > date.today():
self.expire += timedelta(days=pricing.period)
else:
self.expire = date.today() + timedelta(days=pricing.period)

else:
# This should not ever happen (as this case should be managed by plan change request)
# but just in case we consider a case when user has a different plan
if self.expire is None:
if not self.plan.is_free and self.expire is None:
status = True
elif self.expire > date.today():
elif not self.plan.is_free and self.expire > date.today():
status = False
accounts_logger.warning("Account '%s' [id=%d] plan NOT changed to '%s' [id=%d]" % (
self.user, self.user.pk, plan, plan.pk))
Expand Down
3 changes: 3 additions & 0 deletions plans/plan_change.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ def _calculate_day_cost(self, plan, period):
"""
Finds most fitted plan pricing for a given period, and calculate day cost
"""
if plan.is_free():
# If plan is free then cost is always 0
return 0

plan_pricings = plan.planpricing_set.order_by('-pricing__period').select_related('pricing')
selected_pricing = None
Expand Down
6 changes: 5 additions & 1 deletion plans/templates/mail/change_plan_body.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{% load i18n %}{% autoescape off %}
{% trans "Hi" %} {% firstof user.get_full_name user.username %},

{% blocktrans with plan_name=plan.name expire=userplan.expire %}Your current plan is {{ plan_name }} and it will expire on {{ expire }}. {% endblocktrans %}
{% if userplan.expire != None %}
{% blocktrans with plan_name=plan.name expire=userplan.expire %}Your current plan is {{ plan_name }} and it will expire on {{ expire }}. {% endblocktrans %}
{% else %}
{% blocktrans with plan_name=plan.name %}Your current plan is {{ plan_name }}. {% endblocktrans %}
{% endif %}

{% trans "Thank you" %}
--
Expand Down
46 changes: 31 additions & 15 deletions plans/templates/plans/plan_table.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
<th></th>
{% for plan in plan_list %}
<th class="planpricing_footer {% ifequal forloop.counter0 current_userplan_index %}current{% endifequal %}">
{% if plan != userplan.plan and not userplan.is_expired %}
{% if plan != userplan.plan and not userplan.is_expired and not userplan.plan.is_free %}
<a href="{% url 'create_order_plan_change' pk=plan.id %}" class="change_plan">{% trans "Change" %}</a>{% endif %}
</th>
{% endfor %}
Expand All @@ -70,22 +70,38 @@
<th></th>
{% for plan in plan_list %}
<th class="planpricing_footer {% ifequal forloop.counter0 current_userplan_index %}current{% endifequal %}">


{% if plan.available %}
<ul>
{% for plan_pricing in plan.planpricing_set.all %}
<ul>
{% if not plan.is_free %}
{% for plan_pricing in plan.planpricing_set.all %}
<li>
{% if plan_pricing.pricing.url %}<a href="{{ plan_pricing.pricing.url }}" class="info_link pricing">{% endif %}
<span class="plan_pricing_name">{{ plan_pricing.pricing.name }}</span>
<span class="plan_pricing_period">({{ plan_pricing.pricing.period }} {% trans "days" %})</span>
{% if plan_pricing.pricing.url %}</a>{% endif %}
<span class="plan_pricing_price">{{ plan_pricing.price }}&nbsp;{{ CURRENCY }}</span>
{% if plan_pricing.plan == userplan.plan or userplan.is_expired or userplan.plan.is_free %}
<a href="{% url 'create_order_plan' pk=plan_pricing.pk %}" class="buy">{% trans "Buy" %}</a>
{% endif %}
{% endfor %}
{% else %}
{# Allow selecting plans with no pricings #}
<li>
{% if plan_pricing.pricing.url %}<a href="{{ plan_pricing.pricing.url }}" class="info_link pricing">{% endif %}
<span class="plan_pricing_name">{{ plan_pricing.pricing.name }}</span>
<span class="plan_pricing_period">({{ plan_pricing.pricing.period }} {% trans "days" %})</span>
{% if plan_pricing.pricing.url %}</a>{% endif %}
<span class="plan_pricing_price">{{ plan_pricing.price }}&nbsp;{{ CURRENCY }}</span>
{% if plan_pricing.plan == userplan.plan or userplan.is_expired %}
<a href="{% url 'create_order_plan' pk=plan_pricing.pk %}" class="buy">{% trans "Buy" %}</a>
{% endif %}
{% endfor %}
</ul>
<span class="plan_pricing_name">{% trans "Free" %}</span>
<span class="plan_pricing_period">({% trans "no expiry" %})</span>
<span class="plan_pricing_price">0&nbsp;{{ CURRENCY }}</span>
{% if plan != userplan.plan or userplan.is_expired %}
<a href="{% url 'create_order_plan_change' pk=plan.id %}" class="change_plan">
{% if userplan.is_expired %}
{% trans "Select" %}
{% else %}
{% trans "Change" %}
{% endif %}
</a>
{% endif %}
</li>
{% endif %}
</ul>

{% else %}
<span class="plan_not_available">
Expand Down
20 changes: 20 additions & 0 deletions plans/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,26 @@ def test_disable_emails(self):
self.assertEqual(u.userplan.active, True)
self.assertEqual(len(mail.outbox), 0)

def test_switch_to_free_no_expiry(self):
"""
Tests switching to a free Plan and checks that their expiry is cleared
Tests if expire date is set correctly
Tests if mail has been send
Tests if account has been activated
"""
u = User.objects.get(username='test1')
self.assertIsNotNone(u.userplan.expire)

plan = Plan.objects.get(name="Free")
self.assertTrue(plan.is_free())
self.assertNotEqual(u.userplan.plan, plan)

# Switch to Free Plan
u.userplan.extend_account(plan, None)
self.assertEquals(u.userplan.plan, plan)
self.assertIsNone(u.userplan.expire)
self.assertEqual(u.userplan.active, True)


class TestInvoice(TestCase):
fixtures = ['initial_plan', 'test_django-plans_auth', 'test_django-plans_plans']
Expand Down
17 changes: 13 additions & 4 deletions plans/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,12 @@ def get_all_context(self):
Q(plan__customized=self.request.user) | Q(
plan__customized__isnull=True)))


# User is not allowed to create new order for Plan when he has different Plan
# He should use Plan Change View for this kind of action
if not self.request.user.userplan.is_expired() and self.request.user.userplan.plan != self.plan_pricing.plan:
# unless it's a free plan. Otherwise, the should use Plan Change View for this
# kind of action
if not self.request.user.userplan.is_expired() \
and not self.request.user.userplan.plan.is_free() \
and self.request.user.userplan.plan != self.plan_pricing.plan:
raise Http404

self.plan = self.plan_pricing.plan
Expand Down Expand Up @@ -300,7 +302,14 @@ def get_policy(self):

def get_price(self):
policy = self.get_policy()
period = self.request.user.userplan.days_left()
userplan = self.request.user.userplan

if userplan.expire is not None:
period = self.request.user.userplan.days_left()
else:
# Use the default period of the new plan
period = 30

return policy.get_change_price(self.request.user.userplan.plan, self.plan, period)

def get_context_data(self, **kwargs):
Expand Down

0 comments on commit 5f926cc

Please sign in to comment.