├── .gitignore
├── CONTRIBUTORS
├── GPL-LICENSE.txt
├── MANIFEST.in
├── README
├── docs
├── usersubscription-states.dot
└── usersubscription-states.dot.png
├── setup.py
└── subscription
├── __init__.py
├── admin.py
├── locale
└── pl
│ └── LC_MESSAGES
│ └── django.po
├── migrations
├── 0001_initial.py
├── 0002_trial_period.py
├── 0003_auto__chg_field_subscription_group__del_unique_subscription_group.py
└── __init__.py
├── models.py
├── providers.py
├── signals.py
├── tests.py
├── urls.py
├── utils.py
└── views.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.swp
3 | *.egg-info
4 |
--------------------------------------------------------------------------------
/CONTRIBUTORS:
--------------------------------------------------------------------------------
1 | Many thanks to all the contributors!
2 |
3 | CONTRIBUTORS include:
4 | ironfroggy (http://github.com/ironfroggy)
5 | Igor Gumenyuk (http://github.com/exslim)
6 | gearheart (http://github.com/gearheart)
7 | leftpudding (http://github.com/leftpudding)
8 |
--------------------------------------------------------------------------------
/GPL-LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009-2010 saaskit.org
2 | -----------------------------------
3 |
4 | This program is free software: you can redistribute it and/or modify
5 | it under the terms of the GNU General Public License as published by
6 | the Free Software Foundation, either version 3 of the License, or
7 | (at your option) any later version.
8 |
9 | This program is distributed in the hope that it will be useful,
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | GNU General Public License for more details.
13 |
14 | You should have received a copy of the GNU General Public License
15 | along with this program. If not, see .
16 |
17 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | graft *
2 | prune .hg
3 | prune .git
4 | prune .ropeproject
5 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | Django Subscription
2 | ===================
3 |
4 | Django-subscription is an application for handling PayPal-based pay
5 | subscriptions. Module does not handle explicit permissions; instead,
6 | subscribed user are automatically added to predefined groups using
7 | `django.contrib.auth' application. It needs django-paypal application
8 | available at [http://github.com/johnboxall/django-paypal/] for handling
9 | payments.
10 |
11 | Table of Contents
12 | =================
13 | 1 Installation
14 | 2 Settings
15 | 3 Models
16 | 3.1 Subscription
17 | 3.1.1 methods
18 | 3.2 UserSubscription
19 | 3.2.1 methods
20 | 3.3 Transaction
21 | 4 Signals
22 | 5 Views
23 | 6 URLs
24 | 7 Templates
25 | 8 Subscription change
26 | 9 Example code
27 | 10 Bugs and omissions
28 | 10.1 Plans
29 | 11 License
30 |
31 |
32 | 1 Installation
33 | ~~~~~~~~~~~~~~
34 | Copy or symlink `subscription/' directory on Python path (`setup.py'
35 | script for automated installation will be supplied later on). Module
36 | contents are available in the `subscription' module.
37 |
38 | In order to use application, add `subscription' to INSTALLED_APPS in
39 | Django project `settings.py' file.
40 |
41 | 2 Settings
42 | ~~~~~~~~~~
43 | In project's `settings.py' file `SUBSCRIPTION_PAYPAL_SETTINGS'
44 | should be set to a dictionary with default PayPal button settings,
45 | as described in django-paypal documentation. At least the `business'
46 | key should be set to your PayPal business e-mail.
47 |
48 | `SUBSCRIPTION_PAYPAL_FORM' can be set to a form class pathname as
49 | string to use custom PayPal payment button class. Default is
50 | 'paypal.standard.forms.PayPalPaymentsForm'. To use PayPal encrypted
51 | buttons or shared secrets, specify needed django-paypal settings and
52 | set an appropriate class here.
53 |
54 | `SUBSCRIPTION_GRACE_PERIOD' is an integer and it specifies number of
55 | days after individual subscription expiry on which account is
56 | actually treated as expired. Default is 2 days. Intent of this
57 | setting is that recurring payments take place e.g. monthly, so
58 | either on last day of subscription period, or even on first day
59 | after it; this way we avoind unintentionally locking out user
60 | account.
61 |
62 | 3 Models
63 | ~~~~~~~~
64 | Two models defined by the application are available in the
65 | `subscription.models' module.
66 |
67 | 3.1 Subscription
68 | ================
69 | Main model used by the application is `Subscription'. It
70 | represents a single subscription available for users. Subscription
71 | has following fields:
72 | - `name' - short name
73 | - `description' - longer description
74 | - `price' - subscription price
75 | - `recurrence_period' - PayPal subscription recurrence period (used
76 | only if `recurrence_unit' is not `None')
77 | - `recurrence_unit' - in what units is recurrence period expressed:
78 | - D for days
79 | - W for weeks
80 | - M for months
81 | - Y for years
82 | - None (NULL) for one-time (non-recurring) payment
83 | - `group' - one to one relation to
84 | `django.contrib.auth.models.Group'. Subscription is identified
85 | by the group.
86 |
87 | 3.1.1 methods
88 | -------------
89 | - `price_per_day()' - returns estimate subscription price per day,
90 | as a float. This value is used to give user that upgrades
91 | subscription a rebate for unused part of month. Value is only
92 | an estimate: average length of month (30.4368 days) and year
93 | (365.2425 days) are used.
94 | - `get_pricing_display()' - return pretty pricing info for display
95 | as a string.
96 |
97 | 3.2 UserSubscription
98 | ====================
99 | This model instances define a user's subscription. Model has
100 | following fields:
101 | - `user' - one-to-one relation to `auth.User' model, primary key;
102 | - `subscription' - foreign key relation to `Subscription' model,
103 | specifies kind of subscription `user' is subscribed to;
104 | - `expires' - expiry date (if null, subscription never expires)
105 | - `active' - boolean, True if subscription is active
106 | - `cancelled' - boolean, True if subscription was cancelled
107 |
108 | Fields `active' and `cancelled' are used for implementing the
109 | subscription change flow (see later). Every UserSubscription
110 | starts with both `active' and `cancelled' set to `False'. When
111 | PayPal subscription is confirmed, `active' is set to True. When
112 | any other PayPal subscription for the same user is confirmed,
113 | `active' is set to `False' (because `active' is set to `True' for
114 | this other subscription, in other UserSubscription instance). When
115 | subscription is cancelled at PayPal, `cancelled' is set to `True'.
116 | When UserSubscription is cancelled and not active, it is deleted.
117 | When UserSubscription has expired and is cancelled, it is deleted.
118 | Transition graph of these state bits can be found in
119 | [file:docs/usersubscription-states.dot.png] (GraphViz source in
120 | [file:docs/usersubscription-states.dot]).
121 |
122 | Class field `grace_timedelta' is provided (read-only) and contains
123 | effective value of `SUBSCRIPTION_GRACE_PERIOD' setting as
124 | `datetime.timedelta' object.
125 |
126 | 3.2.1 methods
127 | -------------
128 | - `user_is_group_member()' - returns true if `user' is member of
129 | `subscription.group';
130 | - `expired()' - returns true if there is more than
131 | `SUBSCRIPTION_GRACE_PERIOD' days after `expires' date;
132 | - `valid()' - returns true if:
133 | + `expired()' is false and `user_is_group_member()' is false, or
134 | + `expired()' is true and `user_is_group_member()' is true;
135 | - `unsubscribe()' - remove `subscription.group' from `user''s groups
136 | - `subscribe()' - add `subscription.group' to `user''s groups
137 | (called automatically on PayPal one-time payment and
138 | subscription start);
139 | - `fix()' - if not `valid()', call `unsubscribe()' or `subscribe()';
140 | - `extend(timedelta=None)' - extend `expires' field by provided
141 | `datetime.timedelta', or by `subscription''s recurrence period
142 | (called automatically on PayPal subscription payments);
143 | - `try_change(subscription)' - sends `change_check' signal to test
144 | whether change from `self.subscription' to Subscription object
145 | supplied in `subscription' parameter is possible. Returns list
146 | of reasons why upgrade is denied; if list is empty, upgrade is
147 | allowed.
148 |
149 | Convenience function `subscription.models.unsubscribe_expired()'
150 | is also provided. It loops over all expired `UserSubscription'
151 | instances and calls `unsubscribe()' method. It is intended to be
152 | called automatically from cron, django-cron, or on some event.
153 | Alternatively, `fix()' can be called on events related to
154 | user, e.g. on user login.
155 |
156 | 3.3 Transaction
157 | ===============
158 | `Transaction' model is mostly read-only and is used to view
159 | subscription-related events in the admin panel. It has following
160 | fields:
161 | - `timestamp' - date and time of event
162 | - `subscription' - foreign key of `Subscription' model that event
163 | was related to
164 | - `user' - foreign key of `django.contrib.auth.models.User' model
165 | that event was related to
166 | - `ipn' - foreign key of `paypal.standard.ipn.models.PayPalIPN'
167 | model identifying payment callback related to event
168 | - `event' - type of event, one of:
169 | - new usersubscription
170 | - one-time payment
171 | - subscription payment
172 | - unexpected payment
173 | - payment flagged
174 | - deactivated
175 | - activated
176 | - unexpected subscription
177 | - remove subscription
178 | - cancel subscription
179 | - unexpected cancel
180 | - modify subscription
181 | - subscription expired
182 | The "unexpected" events are ones that could not be related to any
183 | specific user/subscription pair.
184 | - `amount' - amount (`mc_gross') of `ipn'
185 | - `comment' - site admin's comment, only field intended to be
186 | modified.
187 | In admin panel's `Transaction' object list, fields `subscription',
188 | `user', `ipn' are links to related modes instance's admin forms.
189 |
190 | 4 Signals
191 | ~~~~~~~~~
192 | On subscription-related events, the application sends signals that
193 | project code can connect to and do some site-specific things (e.g.
194 | send a nice e-mail to user). Signals are available in
195 | `subscription.signals' package. All signals have `Subscription'
196 | instance (or, in extreme cases with `event' signal, `None') as
197 | sender, and have arguments `ipn'
198 | (`paypal.standard.ipn.models.PayPalIPN' model instance), `user'
199 | (`django.contrib.auth.models.User' instance), `subscription'
200 | (`Subscription' instance or None, same as sender),
201 | `usersubscription' (`UserSubscription' instance). Signals are:
202 | - `signed_up' - user signed up for one-time payment,
203 | - `subscribed' - user subscribed
204 | - `unsubscribed' - user unsubscribed from PayPal (`usersubscription'
205 | is a deleted object if `usersubscription.active' is True)
206 | - `paid' - payment received from a subscription
207 | - `event' - other strange event, does not receive `usersubscription'
208 | argument (there is no meaningful `UserSubscription' object) and
209 | receives additional `event' argument, which may be
210 | - `unexpected_payment'
211 | - `flagged'
212 | - `unexpected_subscription'
213 | - `unexpected_cancel'
214 | - `subscription_modify'
215 |
216 | Signal `change_check' is a hook for verification of subscription
217 | change. Sender is `UserSubscription' object with user's current
218 | subscription, additional parameter `subscription' provides
219 | subscription to change to. If subscription change is possible,
220 | listener should return `None', otherwise it should return a string
221 | describing reason that will be displayed to user.
222 |
223 | 5 Views
224 | ~~~~~~~
225 | Views are available in `subscription.views' module
226 | - `subscription_list' lists available subscription using
227 | `subscription/subscription_list.html' template
228 | - `subscription_detail' presents details of the selected
229 | subscription (login is required for this view) along with PayPal
230 | button for subscription or upgrade.
231 |
232 | 6 URLs
233 | ~~~~~~
234 | Module `subscription.urls' configures default urls for module. This
235 | are:
236 | - root URL displays `subscription_list' view
237 | - /id/ (numeric ID) displays `subscription_detail' view for
238 | Subscription with ID /id/
239 | - `paypal/' is PayPal IPN URL
240 | - `done/' displays `subscription/subscription_done.html' template
241 | and is where successful PayPal transactions for initial
242 | subscription are redirected
243 | - `change-done/' displays
244 | `subscription/subscription_change_done.html' template and is
245 | where successful PayPal transactions for subscription change are
246 | redirected
247 | - `cancel/' displays `subscription/subscription_cancel.html'
248 | template and is where cancelled PayPal transactions are redirected
249 |
250 | 7 Templates
251 | ~~~~~~~~~~~
252 | Templates `subscription/subscription_done.html' and
253 | `subscription/subscription_cancel.html' receive no context.
254 |
255 | Template `subscription/subscription_change_dane.html' receives
256 | `cancel_url' parameter, which is URL to PayPal list of transactions
257 | with site's merchant account, making it easier to cancel the old
258 | subscription.
259 |
260 | Template `subscription/subscription_list.html' receives
261 | `object_list' variable which is a list of `Subscription' objects.
262 |
263 | Template `subscription/subscription_detail.html' receives:
264 | - `object' variable which is a `Subscription' object,
265 | - `usersubscription' variable, which is current user's active
266 | `UserSubscription' instance (may be used to tell apart initial
267 | subscription from subscription change/upgrade, or to display
268 | current subscription's expiry date),
269 | - `change_denied_reasons', which is a list of reasons that
270 | subscription change/upgrade is denied; if false (empty list or
271 | `None' if user is not subscribed), change or signup is allowed,
272 | - `form' variable which is a PayPal form for the `object', if
273 | `change_denied_reasons' is false,
274 | - `cancel_url', which is URL to PayPal list of transactions with
275 | site's merchant account, making it easier to cancel the old
276 | subscription.
277 |
278 | 8 Subscription change
279 | ~~~~~~~~~~~~~~~~~~~~~
280 | Most complex flow in this app is when user wants to change (upgrade)
281 | current subscription. For subscriptions we are using PayPal
282 | standard subscriptions API. This means, we get three kinds of
283 | asynchronous IPN notifications:
284 | - subscr_signup when user signs up for new subscription,
285 | - subscr_payment on every single payment,
286 | - subscr_cancel when user or merchant cancels subscription (or
287 | subscr_eot when time-limited subscription runs out; we treat
288 | subscr_eot exactly as subscr_cancel).
289 | When user signs up, we get subscr_signup and subscr_payment for
290 | first payment, in random order. There is no support for changing
291 | running subscription, so user needs to sign up for new subscription
292 | and cancel old one.
293 |
294 | Events for subscriptions are handled this way:
295 | - subscr_payment finds UserSubscription object for User and
296 | Subscription ID specified in the IPN. If UserSubscription is not
297 | found, new one is created, which becomes inactive. Found or new
298 | UserSubscription object is extended for the next billing period.
299 | - subscr_signup finds UserSubscription object for User and
300 | Subscription ID specified in the IPN. If UserSubscription is not
301 | found, new one is created. Found or created UserSubscription is
302 | set to active, User is added to subscription's group; if user has
303 | another UserSubscription, they are made inactive and user is
304 | removed from these Subscription groups. In effect, on signup the
305 | new subscription becomes user's only active one, and its group
306 | only subscription-related group to which user belongs.
307 | - subscr_cancel finds relevant UserSubscription object. If it is
308 | inactive (which means subscription change), removes user from its
309 | subscription's group, and deletes the UserSubscription. If it is
310 | active, does nothing, so user can use up rest of current billing
311 | period.
312 |
313 | So, signup flow is:
314 | - user clicks in PayPal subscribe button displayed on subscription
315 | detail page and subscribes at PayPal,
316 | - subscr_payment extends the UserSubscription,
317 | - subscr_signup makes the UserSubscription active and uncancelled
318 | and adds user to group,
319 | - whichever of those got called first, creates the UserSubscription.
320 |
321 | Cancel flow is:
322 | - user cancels subscription at PayPal,
323 | - UserSubscription is active, so it is marked cancelled, kept and
324 | stays valid until expiry.
325 |
326 | Subscription change flow is:
327 | - If user is allowed to change subscription, subscription detail page
328 | displays PayPal subscribe button,
329 | - user clicks subscribe button and signs up for new subscription at
330 | PayPal,
331 | - landing page after PayPal transaction displays link to PayPal
332 | transaction list which user can use to cancel old subscription at
333 | PayPal,
334 | - user cancels old subscription at PayPal;
335 | - whichever of subscr_payment or subscr_signup gets called first,
336 | creates new, inactive, uncancelled UserSubscription instance,
337 | - subscr_payment extends new UserSubscription instance for next
338 | billing period,
339 | - subscr_signup deactivates all active UserSubscriptions and removes
340 | user from group; then, activates and uncancels new
341 | UserSubscription and adds user to its subscription's group,
342 | - subscr_cancel (which gets called after previous two, because user
343 | needs some time to click through the PayPal forms) finds inactive
344 | UserSubscription, ensures that user is really not member of group,
345 | and deletes the UserSubscription object.
346 |
347 | If user makes a mistake and cancels new subscription instead of the
348 | old one, new subscription goes through "Cancel flow" above, does not
349 | get deleted, so user has chance to fix things at PayPal. Project
350 | should add `signals.unsubscribed' handler that would detect such
351 | situation (if `usersubscription' parameter is active, and user has
352 | inactive UserSubscription objects, cancel was probably a mistake)
353 | and notify user of his mistake.
354 |
355 | 9 Example code
356 | ~~~~~~~~~~~~~~
357 | Example usage and templates are available as `django-saas-kit'
358 | project at [http://github.com/saas-kit/django-saas-kit/]
359 |
360 | 10 Bugs and omissions
361 | ~~~~~~~~~~~~~~~~~~~~~
362 | - There is no `setup.py' script for automated installation.
363 | - No support for PayPal PDT; PDT has only presentational value (IPN
364 | needs to be received anyway, and PDT should be used only to
365 | display transaction details to user on after transaction landing
366 | page), so support for it has been intentionally omitted.
367 |
368 | 10.1 Plans
369 | ==========
370 | - Single payments for subscription, including possibility of
371 | pay-as-you-go scheme
372 |
373 | 11 License
374 | ~~~~~~~~~~
375 | This project is licensed on terms of GPL (GPL-LICENSE.txt) licenses.
376 |
--------------------------------------------------------------------------------
/docs/usersubscription-states.dot:
--------------------------------------------------------------------------------
1 | digraph usersubscription {
2 | 00[label="-active\n-cancelled"];
3 | 01[label="-active\n+cancelled"];
4 | 10[label="+active\n-cancelled"];
5 | 11[label="+active\n+cancelled"];
6 | CREATE[shape="box",style="bold"];
7 | DELETE[shape="box",style="bold"];
8 |
9 | CREATE->00[penwidth=3,arrowType="vee"];
10 | 00->10[label="signup self"];
11 | 10->00[label="signup other"];
12 | 10->11[label="cancel"];
13 | 11->10[label="signup self"];
14 | 00->01[label="cancel"];
15 | 11->01[label="signup other"];
16 | 01->DELETE[penwidth=3,arrowType="vee"];
17 | }
18 |
--------------------------------------------------------------------------------
/docs/usersubscription-states.dot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhaque/django-subscription/be38c513dc6adb4e5080487ebe9e744704f3f4d8/docs/usersubscription-states.dot.png
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | setup(name="django-subscription",
4 | version="0.1",
5 | description="Subscriptions based web app using django-paypal",
6 | author="SaaS kit",
7 | author_email="admin@saaskit.org",
8 | packages=find_packages(),
9 | include_package_data=True,
10 | )
11 |
12 |
--------------------------------------------------------------------------------
/subscription/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhaque/django-subscription/be38c513dc6adb4e5080487ebe9e744704f3f4d8/subscription/__init__.py
--------------------------------------------------------------------------------
/subscription/admin.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from django.contrib import admin
3 | from django.utils.html import conditional_escape as esc
4 |
5 | from models import Subscription, UserSubscription, Transaction
6 |
7 |
8 | def _pricing(sub):
9 | return sub.get_pricing_display()
10 |
11 |
12 | def _trial(sub):
13 | return sub.get_trial_display()
14 |
15 |
16 | class SubscriptionAdmin(admin.ModelAdmin):
17 | list_display = ('name', _pricing, _trial)
18 | admin.site.register(Subscription, SubscriptionAdmin)
19 |
20 |
21 | def _subscription(trans):
22 | if trans.subscription != None:
23 | return u'%s' % (
24 | trans.subscription.pk, esc(trans.subscription))
25 | _subscription.allow_tags = True
26 |
27 |
28 | def _user(trans):
29 | if trans.user != None:
30 | return u'%s' % (
31 | trans.user.pk, esc(trans.user))
32 | _user.allow_tags = True
33 |
34 |
35 | def _ipn(trans):
36 | if trans.ipn != None:
37 | return u'#%s' % (
38 | trans.ipn.pk, trans.ipn.pk)
39 | _ipn.allow_tags = True
40 |
41 |
42 | class UserSubscriptionAdminForm(forms.ModelForm):
43 | class Meta:
44 | model = UserSubscription
45 | fix_group_membership = forms.fields.BooleanField(required=False)
46 | extend_subscription = forms.fields.BooleanField(required=False)
47 |
48 |
49 | class UserSubscriptionAdmin(admin.ModelAdmin):
50 | list_display = ('__unicode__', _user, _subscription, 'active', 'expires', 'valid')
51 | list_display_links = ('__unicode__',)
52 | list_filter = ('active', 'subscription', )
53 | date_hierarchy = 'expires'
54 | form = UserSubscriptionAdminForm
55 | fieldsets = (
56 | (None, {'fields': ('user', 'subscription', 'expires', 'active')}),
57 | ('Actions', {'fields': ('fix_group_membership', 'extend_subscription'),
58 | 'classes': ('collapse',)}),
59 | )
60 |
61 | def save_model(self, request, obj, form, change):
62 | if form.cleaned_data['extend_subscription']:
63 | obj.extend()
64 | if form.cleaned_data['fix_group_membership']:
65 | obj.fix()
66 | obj.save()
67 |
68 | # action for Django-SVN or django-batch-admin app
69 | actions = ('fix', 'extend',)
70 |
71 | def fix(self, request, queryset):
72 | for us in queryset.all():
73 | us.fix()
74 | fix.short_description = 'Fix group membership'
75 |
76 | def extend(self, request, queryset):
77 | for us in queryset.all():
78 | us.extend()
79 | extend.short_description = 'Extend subscription'
80 |
81 | admin.site.register(UserSubscription, UserSubscriptionAdmin)
82 |
83 |
84 | class TransactionAdmin(admin.ModelAdmin):
85 | date_hierarchy = 'timestamp'
86 | list_display = ('timestamp', 'id', 'event', _subscription, _user, _ipn, 'amount', 'comment')
87 | list_display_links = ('timestamp', 'id')
88 | list_filter = ('subscription', 'user')
89 | admin.site.register(Transaction, TransactionAdmin)
90 |
--------------------------------------------------------------------------------
/subscription/locale/pl/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: PACKAGE VERSION\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2009-06-29 17:50+0200\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
19 |
20 | #: models.py:42
21 | msgid "No recurrence"
22 | msgstr ""
23 |
24 | #: models.py:43
25 | msgid "Day"
26 | msgstr ""
27 |
28 | #: models.py:44
29 | msgid "Week"
30 | msgstr ""
31 |
32 | #: models.py:45
33 | msgid "Month"
34 | msgstr ""
35 |
36 | #: models.py:46
37 | msgid "Year"
38 | msgstr ""
39 |
40 | #: models.py:82
41 | #, python-format
42 | msgid "%(price).02f / %(unit)s"
43 | msgid_plural "%(price).02f / %(period)d %(unit_plural)s"
44 | msgstr[0] ""
45 | msgstr[1] ""
46 |
47 | #: models.py:90
48 | #, python-format
49 | msgid "%(price).02f one-time fee"
50 | msgstr ""
51 |
52 | #: models.py:188
53 | msgid "This is your current subscription."
54 | msgstr ""
55 |
--------------------------------------------------------------------------------
/subscription/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 |
2 | from south.db import db
3 | from django.db import models
4 | from subscription.models import *
5 |
6 | class Migration:
7 |
8 | def forwards(self, orm):
9 |
10 | # Adding model 'UserSubscription'
11 | db.create_table('subscription_usersubscription', (
12 | ('id', orm['subscription.UserSubscription:id']),
13 | ('user', orm['subscription.UserSubscription:user']),
14 | ('subscription', orm['subscription.UserSubscription:subscription']),
15 | ('expires', orm['subscription.UserSubscription:expires']),
16 | ('active', orm['subscription.UserSubscription:active']),
17 | ('cancelled', orm['subscription.UserSubscription:cancelled']),
18 | ))
19 | db.send_create_signal('subscription', ['UserSubscription'])
20 |
21 | # Adding model 'Transaction'
22 | db.create_table('subscription_transaction', (
23 | ('id', orm['subscription.Transaction:id']),
24 | ('timestamp', orm['subscription.Transaction:timestamp']),
25 | ('subscription', orm['subscription.Transaction:subscription']),
26 | ('user', orm['subscription.Transaction:user']),
27 | ('ipn', orm['subscription.Transaction:ipn']),
28 | ('event', orm['subscription.Transaction:event']),
29 | ('amount', orm['subscription.Transaction:amount']),
30 | ('comment', orm['subscription.Transaction:comment']),
31 | ))
32 | db.send_create_signal('subscription', ['Transaction'])
33 |
34 | # Adding model 'Subscription'
35 | db.create_table('subscription_subscription', (
36 | ('id', orm['subscription.Subscription:id']),
37 | ('name', orm['subscription.Subscription:name']),
38 | ('description', orm['subscription.Subscription:description']),
39 | ('price', orm['subscription.Subscription:price']),
40 | ('recurrence_period', orm['subscription.Subscription:recurrence_period']),
41 | ('recurrence_unit', orm['subscription.Subscription:recurrence_unit']),
42 | ('group', orm['subscription.Subscription:group']),
43 | ))
44 | db.send_create_signal('subscription', ['Subscription'])
45 |
46 | # Creating unique_together for [user, subscription] on UserSubscription.
47 | db.create_unique('subscription_usersubscription', ['user_id', 'subscription_id'])
48 |
49 |
50 |
51 | def backwards(self, orm):
52 |
53 | # Deleting model 'UserSubscription'
54 | db.delete_table('subscription_usersubscription')
55 |
56 | # Deleting model 'Transaction'
57 | db.delete_table('subscription_transaction')
58 |
59 | # Deleting model 'Subscription'
60 | db.delete_table('subscription_subscription')
61 |
62 | # Deleting unique_together for [user, subscription] on UserSubscription.
63 | db.delete_unique('subscription_usersubscription', ['user_id', 'subscription_id'])
64 |
65 |
66 |
67 | models = {
68 | 'auth.group': {
69 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
70 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
71 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})
72 | },
73 | 'auth.permission': {
74 | 'Meta': {'unique_together': "(('content_type', 'codename'),)"},
75 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
76 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
77 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
78 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
79 | },
80 | 'auth.user': {
81 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
82 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
83 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
84 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),
85 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
86 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
87 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
88 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
89 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
90 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
91 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
92 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),
93 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
94 | },
95 | 'contenttypes.contenttype': {
96 | 'Meta': {'unique_together': "(('app_label', 'model'),)", 'db_table': "'django_content_type'"},
97 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
98 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
99 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
100 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
101 | },
102 | 'ipn.paypalipn': {
103 | 'Meta': {'db_table': "'paypal_ipn'"},
104 | 'address_city': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
105 | 'address_country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
106 | 'address_country_code': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
107 | 'address_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
108 | 'address_state': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
109 | 'address_status': ('django.db.models.fields.CharField', [], {'max_length': '11', 'blank': 'True'}),
110 | 'address_street': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
111 | 'address_zip': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
112 | 'amount': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
113 | 'amount1': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
114 | 'amount2': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
115 | 'amount3': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
116 | 'amount_per_cycle': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
117 | 'auction_buyer_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
118 | 'auction_closing_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
119 | 'auction_multi_item': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}),
120 | 'auth_amount': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
121 | 'auth_exp': ('django.db.models.fields.CharField', [], {'max_length': '28', 'blank': 'True'}),
122 | 'auth_id': ('django.db.models.fields.CharField', [], {'max_length': '19', 'blank': 'True'}),
123 | 'auth_status': ('django.db.models.fields.CharField', [], {'max_length': '9', 'blank': 'True'}),
124 | 'business': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
125 | 'case_creation_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
126 | 'case_id': ('django.db.models.fields.CharField', [], {'max_length': '14', 'blank': 'True'}),
127 | 'case_type': ('django.db.models.fields.CharField', [], {'max_length': '24', 'blank': 'True'}),
128 | 'charset': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
129 | 'contact_phone': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
130 | 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
131 | 'currency_code': ('django.db.models.fields.CharField', [], {'default': "'USD'", 'max_length': '32', 'blank': 'True'}),
132 | 'custom': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
133 | 'exchange_rate': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '16', 'blank': 'True'}),
134 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
135 | 'flag': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
136 | 'flag_code': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
137 | 'flag_info': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
138 | 'for_auction': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
139 | 'handling_amount': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
140 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
141 | 'initial_payment_amount': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
142 | 'invoice': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
143 | 'ipaddress': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'blank': 'True'}),
144 | 'item_name': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
145 | 'item_number': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
146 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
147 | 'mc_amount1': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
148 | 'mc_amount2': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
149 | 'mc_amount3': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
150 | 'mc_currency': ('django.db.models.fields.CharField', [], {'default': "'USD'", 'max_length': '32', 'blank': 'True'}),
151 | 'mc_fee': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
152 | 'mc_gross': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
153 | 'mc_handling': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
154 | 'mc_shipping': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
155 | 'memo': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
156 | 'next_payment_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
157 | 'notify_version': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
158 | 'num_cart_items': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}),
159 | 'option_name1': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
160 | 'option_name2': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
161 | 'outstanding_balance': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
162 | 'parent_txn_id': ('django.db.models.fields.CharField', [], {'max_length': '19', 'blank': 'True'}),
163 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '24', 'blank': 'True'}),
164 | 'payer_business_name': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
165 | 'payer_email': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
166 | 'payer_id': ('django.db.models.fields.CharField', [], {'max_length': '13', 'blank': 'True'}),
167 | 'payer_status': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'}),
168 | 'payment_cycle': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
169 | 'payment_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
170 | 'payment_gross': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
171 | 'payment_status': ('django.db.models.fields.CharField', [], {'max_length': '9', 'blank': 'True'}),
172 | 'payment_type': ('django.db.models.fields.CharField', [], {'max_length': '7', 'blank': 'True'}),
173 | 'pending_reason': ('django.db.models.fields.CharField', [], {'max_length': '14', 'blank': 'True'}),
174 | 'period1': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
175 | 'period2': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
176 | 'period3': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
177 | 'period_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
178 | 'product_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
179 | 'product_type': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
180 | 'profile_status': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
181 | 'protection_eligibility': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
182 | 'quantity': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}),
183 | 'query': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
184 | 'reason_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}),
185 | 'reattempt': ('django.db.models.fields.CharField', [], {'max_length': '1', 'blank': 'True'}),
186 | 'receipt_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
187 | 'receiver_email': ('django.db.models.fields.EmailField', [], {'max_length': '127', 'blank': 'True'}),
188 | 'receiver_id': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
189 | 'recur_times': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}),
190 | 'recurring': ('django.db.models.fields.CharField', [], {'max_length': '1', 'blank': 'True'}),
191 | 'recurring_payment_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
192 | 'remaining_settle': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
193 | 'residence_country': ('django.db.models.fields.CharField', [], {'max_length': '2', 'blank': 'True'}),
194 | 'response': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
195 | 'retry_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
196 | 'rp_invoice_id': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
197 | 'settle_amount': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
198 | 'settle_currency': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
199 | 'shipping': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
200 | 'shipping_method': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
201 | 'subscr_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
202 | 'subscr_effective': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
203 | 'subscr_id': ('django.db.models.fields.CharField', [], {'max_length': '19', 'blank': 'True'}),
204 | 'tax': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
205 | 'test_ipn': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
206 | 'time_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
207 | 'transaction_entity': ('django.db.models.fields.CharField', [], {'max_length': '7', 'blank': 'True'}),
208 | 'transaction_subject': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
209 | 'txn_id': ('django.db.models.fields.CharField', [], {'max_length': '19', 'blank': 'True'}),
210 | 'txn_type': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
211 | 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
212 | 'username': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
213 | 'verify_sign': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'})
214 | },
215 | 'subscription.subscription': {
216 | 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
217 | 'group': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.Group']", 'unique': 'True'}),
218 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
219 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
220 | 'price': ('django.db.models.fields.DecimalField', [], {'max_digits': '64', 'decimal_places': '2'}),
221 | 'recurrence_period': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
222 | 'recurrence_unit': ('django.db.models.fields.CharField', [], {'max_length': '1', 'null': 'True'})
223 | },
224 | 'subscription.transaction': {
225 | 'amount': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
226 | 'comment': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
227 | 'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
228 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
229 | 'ipn': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ipn.PayPalIPN']", 'null': 'True', 'blank': 'True'}),
230 | 'subscription': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['subscription.Subscription']", 'null': 'True', 'blank': 'True'}),
231 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
232 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
233 | },
234 | 'subscription.usersubscription': {
235 | 'Meta': {'unique_together': "(('user', 'subscription'),)"},
236 | 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
237 | 'cancelled': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
238 | 'expires': ('django.db.models.fields.DateField', [], {'default': 'datetime.date.today', 'null': 'True'}),
239 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
240 | 'subscription': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['subscription.Subscription']"}),
241 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
242 | }
243 | }
244 |
245 | complete_apps = ['subscription']
246 |
--------------------------------------------------------------------------------
/subscription/migrations/0002_trial_period.py:
--------------------------------------------------------------------------------
1 |
2 | from south.db import db
3 | from django.db import models
4 | from subscription.models import *
5 |
6 | class Migration:
7 |
8 | def forwards(self, orm):
9 |
10 | # Adding field 'Subscription.trial_period'
11 | db.add_column('subscription_subscription', 'trial_period', orm['subscription.subscription:trial_period'])
12 |
13 | # Adding field 'Subscription.trial_unit'
14 | db.add_column('subscription_subscription', 'trial_unit', orm['subscription.subscription:trial_unit'])
15 |
16 |
17 |
18 | def backwards(self, orm):
19 |
20 | # Deleting field 'Subscription.trial_period'
21 | db.delete_column('subscription_subscription', 'trial_period')
22 |
23 | # Deleting field 'Subscription.trial_unit'
24 | db.delete_column('subscription_subscription', 'trial_unit')
25 |
26 |
27 |
28 | models = {
29 | 'auth.group': {
30 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
31 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
32 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})
33 | },
34 | 'auth.permission': {
35 | 'Meta': {'unique_together': "(('content_type', 'codename'),)"},
36 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
37 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
38 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
39 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
40 | },
41 | 'auth.user': {
42 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
43 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
44 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
45 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),
46 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
47 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
48 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
49 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
50 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
51 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
52 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
53 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),
54 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
55 | },
56 | 'contenttypes.contenttype': {
57 | 'Meta': {'unique_together': "(('app_label', 'model'),)", 'db_table': "'django_content_type'"},
58 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
59 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
60 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
61 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
62 | },
63 | 'ipn.paypalipn': {
64 | 'Meta': {'db_table': "'paypal_ipn'"},
65 | 'address_city': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
66 | 'address_country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
67 | 'address_country_code': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
68 | 'address_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
69 | 'address_state': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
70 | 'address_status': ('django.db.models.fields.CharField', [], {'max_length': '11', 'blank': 'True'}),
71 | 'address_street': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
72 | 'address_zip': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
73 | 'amount': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
74 | 'amount1': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
75 | 'amount2': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
76 | 'amount3': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
77 | 'amount_per_cycle': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
78 | 'auction_buyer_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
79 | 'auction_closing_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
80 | 'auction_multi_item': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}),
81 | 'auth_amount': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
82 | 'auth_exp': ('django.db.models.fields.CharField', [], {'max_length': '28', 'blank': 'True'}),
83 | 'auth_id': ('django.db.models.fields.CharField', [], {'max_length': '19', 'blank': 'True'}),
84 | 'auth_status': ('django.db.models.fields.CharField', [], {'max_length': '9', 'blank': 'True'}),
85 | 'business': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
86 | 'case_creation_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
87 | 'case_id': ('django.db.models.fields.CharField', [], {'max_length': '14', 'blank': 'True'}),
88 | 'case_type': ('django.db.models.fields.CharField', [], {'max_length': '24', 'blank': 'True'}),
89 | 'charset': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
90 | 'contact_phone': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
91 | 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
92 | 'currency_code': ('django.db.models.fields.CharField', [], {'default': "'USD'", 'max_length': '32', 'blank': 'True'}),
93 | 'custom': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
94 | 'exchange_rate': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '16', 'blank': 'True'}),
95 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
96 | 'flag': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
97 | 'flag_code': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
98 | 'flag_info': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
99 | 'for_auction': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
100 | 'handling_amount': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
101 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
102 | 'initial_payment_amount': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
103 | 'invoice': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
104 | 'ipaddress': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'blank': 'True'}),
105 | 'item_name': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
106 | 'item_number': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
107 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
108 | 'mc_amount1': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
109 | 'mc_amount2': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
110 | 'mc_amount3': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
111 | 'mc_currency': ('django.db.models.fields.CharField', [], {'default': "'USD'", 'max_length': '32', 'blank': 'True'}),
112 | 'mc_fee': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
113 | 'mc_gross': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
114 | 'mc_handling': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
115 | 'mc_shipping': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
116 | 'memo': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
117 | 'next_payment_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
118 | 'notify_version': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
119 | 'num_cart_items': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}),
120 | 'option_name1': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
121 | 'option_name2': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
122 | 'outstanding_balance': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
123 | 'parent_txn_id': ('django.db.models.fields.CharField', [], {'max_length': '19', 'blank': 'True'}),
124 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '24', 'blank': 'True'}),
125 | 'payer_business_name': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
126 | 'payer_email': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
127 | 'payer_id': ('django.db.models.fields.CharField', [], {'max_length': '13', 'blank': 'True'}),
128 | 'payer_status': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'}),
129 | 'payment_cycle': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
130 | 'payment_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
131 | 'payment_gross': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
132 | 'payment_status': ('django.db.models.fields.CharField', [], {'max_length': '9', 'blank': 'True'}),
133 | 'payment_type': ('django.db.models.fields.CharField', [], {'max_length': '7', 'blank': 'True'}),
134 | 'pending_reason': ('django.db.models.fields.CharField', [], {'max_length': '14', 'blank': 'True'}),
135 | 'period1': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
136 | 'period2': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
137 | 'period3': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
138 | 'period_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
139 | 'product_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
140 | 'product_type': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
141 | 'profile_status': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
142 | 'protection_eligibility': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
143 | 'quantity': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}),
144 | 'query': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
145 | 'reason_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}),
146 | 'reattempt': ('django.db.models.fields.CharField', [], {'max_length': '1', 'blank': 'True'}),
147 | 'receipt_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
148 | 'receiver_email': ('django.db.models.fields.EmailField', [], {'max_length': '127', 'blank': 'True'}),
149 | 'receiver_id': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
150 | 'recur_times': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}),
151 | 'recurring': ('django.db.models.fields.CharField', [], {'max_length': '1', 'blank': 'True'}),
152 | 'recurring_payment_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
153 | 'remaining_settle': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
154 | 'residence_country': ('django.db.models.fields.CharField', [], {'max_length': '2', 'blank': 'True'}),
155 | 'response': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
156 | 'retry_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
157 | 'rp_invoice_id': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
158 | 'settle_amount': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
159 | 'settle_currency': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
160 | 'shipping': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
161 | 'shipping_method': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
162 | 'subscr_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
163 | 'subscr_effective': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
164 | 'subscr_id': ('django.db.models.fields.CharField', [], {'max_length': '19', 'blank': 'True'}),
165 | 'tax': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
166 | 'test_ipn': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
167 | 'time_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
168 | 'transaction_entity': ('django.db.models.fields.CharField', [], {'max_length': '7', 'blank': 'True'}),
169 | 'transaction_subject': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
170 | 'txn_id': ('django.db.models.fields.CharField', [], {'max_length': '19', 'blank': 'True'}),
171 | 'txn_type': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
172 | 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
173 | 'username': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
174 | 'verify_sign': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'})
175 | },
176 | 'subscription.subscription': {
177 | 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
178 | 'group': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.Group']", 'unique': 'True'}),
179 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
180 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
181 | 'price': ('django.db.models.fields.DecimalField', [], {'max_digits': '64', 'decimal_places': '2'}),
182 | 'recurrence_period': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
183 | 'recurrence_unit': ('django.db.models.fields.CharField', [], {'max_length': '1', 'null': 'True'}),
184 | 'trial_period': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
185 | 'trial_unit': ('django.db.models.fields.CharField', [], {'max_length': '1', 'null': 'True'})
186 | },
187 | 'subscription.transaction': {
188 | 'amount': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
189 | 'comment': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
190 | 'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
191 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
192 | 'ipn': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ipn.PayPalIPN']", 'null': 'True', 'blank': 'True'}),
193 | 'subscription': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['subscription.Subscription']", 'null': 'True', 'blank': 'True'}),
194 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
195 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
196 | },
197 | 'subscription.usersubscription': {
198 | 'Meta': {'unique_together': "(('user', 'subscription'),)"},
199 | 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
200 | 'cancelled': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
201 | 'expires': ('django.db.models.fields.DateField', [], {'default': 'datetime.date.today', 'null': 'True'}),
202 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
203 | 'subscription': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['subscription.Subscription']"}),
204 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
205 | }
206 | }
207 |
208 | complete_apps = ['subscription']
209 |
--------------------------------------------------------------------------------
/subscription/migrations/0003_auto__chg_field_subscription_group__del_unique_subscription_group.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import datetime
3 | from south.db import db
4 | from south.v2 import SchemaMigration
5 | from django.db import models
6 |
7 |
8 | class Migration(SchemaMigration):
9 |
10 | def forwards(self, orm):
11 | # Removing unique constraint on 'subscription', fields ['group']
12 | db.delete_unique(u'subscription_subscription', ['group_id'])
13 |
14 |
15 | # Changing field 'Subscription.group'
16 | db.alter_column(u'subscription_subscription', 'group_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.Group']))
17 |
18 | def backwards(self, orm):
19 |
20 | # Changing field 'Subscription.group'
21 | db.alter_column(u'subscription_subscription', 'group_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['auth.Group'], unique=True))
22 | # Adding unique constraint on 'subscription', fields ['group']
23 | db.create_unique(u'subscription_subscription', ['group_id'])
24 |
25 |
26 | models = {
27 | u'auth.group': {
28 | 'Meta': {'object_name': 'Group'},
29 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
30 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
31 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
32 | },
33 | u'auth.permission': {
34 | 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
35 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
36 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
37 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
38 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
39 | },
40 | u'auth.user': {
41 | 'Meta': {'object_name': 'User'},
42 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
43 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
44 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
45 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
46 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
47 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
48 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
49 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
50 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
51 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
52 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
53 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
54 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
55 | },
56 | u'contenttypes.contenttype': {
57 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
58 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
59 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
60 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
61 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
62 | },
63 | u'ipn.paypalipn': {
64 | 'Meta': {'object_name': 'PayPalIPN', 'db_table': "'paypal_ipn'"},
65 | 'address_city': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
66 | 'address_country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
67 | 'address_country_code': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
68 | 'address_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
69 | 'address_state': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
70 | 'address_status': ('django.db.models.fields.CharField', [], {'max_length': '11', 'blank': 'True'}),
71 | 'address_street': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}),
72 | 'address_zip': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
73 | 'amount': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
74 | 'amount1': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
75 | 'amount2': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
76 | 'amount3': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
77 | 'amount_per_cycle': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
78 | 'auction_buyer_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
79 | 'auction_closing_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
80 | 'auction_multi_item': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}),
81 | 'auth_amount': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
82 | 'auth_exp': ('django.db.models.fields.CharField', [], {'max_length': '28', 'blank': 'True'}),
83 | 'auth_id': ('django.db.models.fields.CharField', [], {'max_length': '19', 'blank': 'True'}),
84 | 'auth_status': ('django.db.models.fields.CharField', [], {'max_length': '9', 'blank': 'True'}),
85 | 'business': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
86 | 'case_creation_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
87 | 'case_id': ('django.db.models.fields.CharField', [], {'max_length': '14', 'blank': 'True'}),
88 | 'case_type': ('django.db.models.fields.CharField', [], {'max_length': '24', 'blank': 'True'}),
89 | 'charset': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
90 | 'contact_phone': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
91 | 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
92 | 'currency_code': ('django.db.models.fields.CharField', [], {'default': "'USD'", 'max_length': '32', 'blank': 'True'}),
93 | 'custom': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
94 | 'exchange_rate': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '16', 'blank': 'True'}),
95 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
96 | 'flag': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
97 | 'flag_code': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}),
98 | 'flag_info': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
99 | 'for_auction': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
100 | 'from_view': ('django.db.models.fields.CharField', [], {'max_length': '6', 'null': 'True', 'blank': 'True'}),
101 | 'handling_amount': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
102 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
103 | 'initial_payment_amount': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
104 | 'invoice': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
105 | 'ipaddress': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'blank': 'True'}),
106 | 'item_name': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
107 | 'item_number': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
108 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
109 | 'mc_amount1': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
110 | 'mc_amount2': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
111 | 'mc_amount3': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
112 | 'mc_currency': ('django.db.models.fields.CharField', [], {'default': "'USD'", 'max_length': '32', 'blank': 'True'}),
113 | 'mc_fee': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
114 | 'mc_gross': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
115 | 'mc_handling': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
116 | 'mc_shipping': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
117 | 'memo': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
118 | 'next_payment_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
119 | 'notify_version': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
120 | 'num_cart_items': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}),
121 | 'option_name1': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
122 | 'option_name2': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
123 | 'outstanding_balance': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
124 | 'parent_txn_id': ('django.db.models.fields.CharField', [], {'max_length': '19', 'blank': 'True'}),
125 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '24', 'blank': 'True'}),
126 | 'payer_business_name': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
127 | 'payer_email': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
128 | 'payer_id': ('django.db.models.fields.CharField', [], {'max_length': '13', 'blank': 'True'}),
129 | 'payer_status': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'}),
130 | 'payment_cycle': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
131 | 'payment_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
132 | 'payment_gross': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
133 | 'payment_status': ('django.db.models.fields.CharField', [], {'max_length': '9', 'blank': 'True'}),
134 | 'payment_type': ('django.db.models.fields.CharField', [], {'max_length': '7', 'blank': 'True'}),
135 | 'pending_reason': ('django.db.models.fields.CharField', [], {'max_length': '14', 'blank': 'True'}),
136 | 'period1': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
137 | 'period2': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
138 | 'period3': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
139 | 'period_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
140 | 'product_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
141 | 'product_type': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
142 | 'profile_status': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
143 | 'protection_eligibility': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
144 | 'quantity': ('django.db.models.fields.IntegerField', [], {'default': '1', 'null': 'True', 'blank': 'True'}),
145 | 'query': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
146 | 'reason_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}),
147 | 'reattempt': ('django.db.models.fields.CharField', [], {'max_length': '1', 'blank': 'True'}),
148 | 'receipt_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
149 | 'receiver_email': ('django.db.models.fields.EmailField', [], {'max_length': '127', 'blank': 'True'}),
150 | 'receiver_id': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
151 | 'recur_times': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}),
152 | 'recurring': ('django.db.models.fields.CharField', [], {'max_length': '1', 'blank': 'True'}),
153 | 'recurring_payment_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
154 | 'remaining_settle': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
155 | 'residence_country': ('django.db.models.fields.CharField', [], {'max_length': '2', 'blank': 'True'}),
156 | 'response': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
157 | 'retry_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
158 | 'rp_invoice_id': ('django.db.models.fields.CharField', [], {'max_length': '127', 'blank': 'True'}),
159 | 'settle_amount': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
160 | 'settle_currency': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
161 | 'shipping': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
162 | 'shipping_method': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
163 | 'subscr_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
164 | 'subscr_effective': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
165 | 'subscr_id': ('django.db.models.fields.CharField', [], {'max_length': '19', 'blank': 'True'}),
166 | 'tax': ('django.db.models.fields.DecimalField', [], {'default': '0', 'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
167 | 'test_ipn': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
168 | 'time_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
169 | 'transaction_entity': ('django.db.models.fields.CharField', [], {'max_length': '7', 'blank': 'True'}),
170 | 'transaction_subject': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
171 | 'txn_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '19', 'blank': 'True'}),
172 | 'txn_type': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
173 | 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
174 | 'username': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
175 | 'verify_sign': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'})
176 | },
177 | u'subscription.subscription': {
178 | 'Meta': {'ordering': "('price', '-recurrence_period')", 'object_name': 'Subscription'},
179 | 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
180 | 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.Group']"}),
181 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
182 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
183 | 'price': ('django.db.models.fields.DecimalField', [], {'max_digits': '64', 'decimal_places': '2'}),
184 | 'recurrence_period': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
185 | 'recurrence_unit': ('django.db.models.fields.CharField', [], {'max_length': '1', 'null': 'True'}),
186 | 'trial_period': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
187 | 'trial_unit': ('django.db.models.fields.CharField', [], {'max_length': '1', 'null': 'True'})
188 | },
189 | u'subscription.transaction': {
190 | 'Meta': {'ordering': "('-timestamp',)", 'object_name': 'Transaction'},
191 | 'amount': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '64', 'decimal_places': '2', 'blank': 'True'}),
192 | 'comment': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
193 | 'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
194 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
195 | 'ipn': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['ipn.PayPalIPN']", 'null': 'True', 'blank': 'True'}),
196 | 'subscription': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['subscription.Subscription']", 'null': 'True', 'blank': 'True'}),
197 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
198 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
199 | },
200 | u'subscription.usersubscription': {
201 | 'Meta': {'unique_together': "(('user', 'subscription'),)", 'object_name': 'UserSubscription'},
202 | 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
203 | 'cancelled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
204 | 'expires': ('django.db.models.fields.DateField', [], {'default': 'datetime.date.today', 'null': 'True'}),
205 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
206 | 'subscription': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['subscription.Subscription']"}),
207 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
208 | }
209 | }
210 |
211 | complete_apps = ['subscription']
--------------------------------------------------------------------------------
/subscription/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhaque/django-subscription/be38c513dc6adb4e5080487ebe9e744704f3f4d8/subscription/migrations/__init__.py
--------------------------------------------------------------------------------
/subscription/models.py:
--------------------------------------------------------------------------------
1 | import datetime
2 |
3 | from django.conf import settings
4 | from django.db import models
5 | from django.contrib import auth
6 | from django.utils.translation import ugettext as _, ungettext, ugettext_lazy
7 |
8 | from paypal.standard import ipn
9 |
10 | import signals
11 | import utils
12 |
13 |
14 | class Transaction(models.Model):
15 | timestamp = models.DateTimeField(auto_now_add=True, editable=False)
16 | subscription = models.ForeignKey('subscription.Subscription',
17 | null=True, blank=True, editable=False)
18 | user = models.ForeignKey(auth.models.User,
19 | null=True, blank=True, editable=False)
20 | ipn = models.ForeignKey(ipn.models.PayPalIPN,
21 | null=True, blank=True, editable=False)
22 | event = models.CharField(max_length=100, editable=False)
23 | amount = models.DecimalField(max_digits=64, decimal_places=2,
24 | null=True, blank=True, editable=False)
25 | comment = models.TextField(blank=True, default='')
26 |
27 | class Meta:
28 | ordering = ('-timestamp',)
29 |
30 |
31 | _recurrence_unit_days = {
32 | 'D': 1.,
33 | 'W': 7.,
34 | 'M': 30.4368, # http://en.wikipedia.org/wiki/Month#Julian_and_Gregorian_calendars
35 | 'Y': 365.2425, # http://en.wikipedia.org/wiki/Year#Calendar_year
36 | }
37 |
38 | _TIME_UNIT_CHOICES = (
39 | ('0', ugettext_lazy('No trial')),
40 | ('D', ugettext_lazy('Day')),
41 | ('W', ugettext_lazy('Week')),
42 | ('M', ugettext_lazy('Month')),
43 | ('Y', ugettext_lazy('Year')),
44 | )
45 |
46 |
47 | class Subscription(models.Model):
48 | name = models.CharField(max_length=100, unique=True, null=False)
49 | description = models.TextField(blank=True)
50 | price = models.DecimalField(max_digits=64, decimal_places=2)
51 | trial_period = models.PositiveIntegerField(null=True, blank=True)
52 | trial_unit = models.CharField(max_length=1, null=True, choices=_TIME_UNIT_CHOICES)
53 | recurrence_period = models.PositiveIntegerField(null=True, blank=True)
54 | recurrence_unit = models.CharField(max_length=1, null=True,
55 | choices=((None, ugettext_lazy("No recurrence")),)
56 | + _TIME_UNIT_CHOICES)
57 | group = models.ForeignKey(auth.models.Group, null=False, blank=False, unique=False)
58 |
59 | _PLURAL_UNITS = {
60 | '0': ugettext_lazy('No trial'),
61 | 'D': 'days',
62 | 'W': 'weeks',
63 | 'M': 'months',
64 | 'Y': 'years',
65 | }
66 |
67 | class Meta:
68 | ordering = ('price', '-recurrence_period')
69 |
70 | def __unicode__(self):
71 | return self.name
72 |
73 | def price_per_day(self):
74 | """Return estimate subscription price per day, as a float.
75 |
76 | This is used to charge difference when user changes
77 | subscription. Price returned is an estimate; month length
78 | used is 30.4368 days, year length is 365.2425 days (averages
79 | including leap years). One-time payments return 0.
80 | """
81 | if self.recurrence_unit is None:
82 | return 0
83 | return float(self.price) / (
84 | self.recurrence_period * _recurrence_unit_days[self.recurrence_unit]
85 | )
86 |
87 | @models.permalink
88 | def get_absolute_url(self):
89 | return ('subscription_detail', (), dict(object_id=str(self.id)))
90 |
91 | def get_pricing_display(self):
92 | if not self.price:
93 | return u'Free'
94 | elif self.recurrence_period:
95 | return ungettext('%(price).02f / %(unit)s',
96 | '%(price).02f / %(period)d %(unit_plural)s',
97 | self.recurrence_period) % {
98 | 'price': self.price,
99 | 'unit': self.get_recurrence_unit_display(),
100 | 'unit_plural': _(self._PLURAL_UNITS[self.recurrence_unit],),
101 | 'period': self.recurrence_period,
102 | }
103 | else:
104 | return _('%(price).02f one-time fee') % {'price': self.price}
105 |
106 | def get_trial_display(self):
107 | if self.trial_period:
108 | return ungettext('One %(unit)s',
109 | '%(period)d %(unit_plural)s',
110 | self.trial_period) % {
111 | 'unit': self.get_trial_unit_display().lower(),
112 | 'unit_plural': _(self._PLURAL_UNITS[self.trial_unit],),
113 | 'period': self.trial_period,
114 | }
115 | else:
116 | return _("No trial")
117 |
118 | def save(self, *args, **kwargs):
119 | """
120 | Set trial period to 0 if the trial unit is 0
121 | """
122 | if self.trial_unit == "0":
123 | self.trial_period = 0
124 |
125 | super(Subscription, self).save(*args, **kwargs)
126 |
127 |
128 | # add User.get_subscription() method
129 | def __user_get_subscription(user):
130 | if not hasattr(user, '_subscription_cache'):
131 | sl = Subscription.objects.filter(group__in=user.groups.all())[:1]
132 | if sl:
133 | user._subscription_cache = sl[0]
134 | else:
135 | user._subscription_cache = None
136 | return user._subscription_cache
137 | auth.models.User.add_to_class('get_subscription', __user_get_subscription)
138 |
139 |
140 | class ActiveUSManager(models.Manager):
141 | """Custom Manager for UserSubscription that returns only live US objects."""
142 | def get_query_set(self):
143 | return super(ActiveUSManager, self).get_query_set().filter(active=True)
144 |
145 |
146 | class UserSubscription(models.Model):
147 | user = models.ForeignKey(auth.models.User)
148 | subscription = models.ForeignKey(Subscription)
149 | expires = models.DateField(null=True, default=datetime.date.today)
150 | active = models.BooleanField(default=True)
151 | cancelled = models.BooleanField(default=True)
152 |
153 | objects = models.Manager()
154 | active_objects = ActiveUSManager()
155 |
156 | grace_timedelta = datetime.timedelta(
157 | getattr(settings, 'SUBSCRIPTION_GRACE_PERIOD', 2))
158 |
159 | class Meta:
160 | unique_together = (('user', 'subscription'), )
161 |
162 | def user_is_group_member(self):
163 | "Returns True is user is member of subscription's group"
164 | return self.subscription.group in self.user.groups.all()
165 | user_is_group_member.boolean = True
166 |
167 | def expired(self):
168 | """Returns true if there is more than SUBSCRIPTION_GRACE_PERIOD
169 | days after expiration date."""
170 | return self.expires is not None and (
171 | self.expires + self.grace_timedelta < datetime.date.today())
172 | expired.boolean = True
173 |
174 | def valid(self):
175 | """Validate group membership.
176 |
177 | Returns True if not expired and user is in group, or expired
178 | and user is not in group."""
179 | if self.expired() or not self.active:
180 | return not self.user_is_group_member()
181 | else:
182 | return self.user_is_group_member()
183 | valid.boolean = True
184 |
185 | def unsubscribe(self):
186 | """Unsubscribe user."""
187 | self.user.groups.remove(self.subscription.group)
188 | self.user.save()
189 |
190 | def subscribe(self):
191 | """Subscribe user."""
192 | self.user.groups.add(self.subscription.group)
193 | self.user.save()
194 |
195 | def fix(self):
196 | """Fix group membership if not valid()."""
197 | if not self.valid():
198 | if self.expired() or not self.active:
199 | self.unsubscribe()
200 | Transaction(user=self.user, subscription=self.subscription, ipn=None,
201 | event='subscription expired'
202 | ).save()
203 | if self.cancelled:
204 | self.delete()
205 | Transaction(user=self.user, subscription=self.subscription, ipn=None,
206 | event='remove subscription (expired)'
207 | ).save()
208 | else:
209 | self.subscribe()
210 |
211 | def extend(self, timedelta=None):
212 | """Extend subscription by `timedelta' or by subscription's
213 | recurrence period."""
214 | if timedelta is not None:
215 | self.expires += timedelta
216 | else:
217 | if self.subscription.recurrence_unit:
218 | self.expires = utils.extend_date_by(
219 | self.expires,
220 | self.subscription.recurrence_period,
221 | self.subscription.recurrence_unit)
222 | else:
223 | self.expires = None
224 |
225 | def try_change(self, subscription):
226 | """Check whether upgrading/downgrading to `subscription' is possible.
227 |
228 | If subscription change is possible, returns false value; if
229 | change is impossible, returns a list of reasons to display.
230 |
231 | Checks are performed by sending
232 | subscription.signals.change_check with sender being
233 | UserSubscription object, and additional parameter
234 | `subscription' being new Subscription instance. Signal
235 | listeners should return None if change is possible, or a
236 | reason to display.
237 | """
238 | if self.subscription == subscription:
239 | if self.active and self.cancelled:
240 | return None # allow resubscribing
241 | return [_(u'This is your current subscription.')]
242 | return [
243 | res[1]
244 | for res in signals.change_check.send(
245 | self, subscription=subscription)
246 | if res[1]]
247 |
248 | @models.permalink
249 | def get_absolute_url(self):
250 | return ('subscription_usersubscription_detail', (), dict(object_id=str(self.id)))
251 |
252 | def __unicode__(self):
253 | rv = u"%s's %s" % (self.user, self.subscription)
254 | if self.expired():
255 | rv += u' (expired)'
256 | return rv
257 |
258 |
259 | def unsubscribe_expired():
260 | """Unsubscribes all users whose subscription has expired.
261 | Loops through all UserSubscription objects with `expires' field
262 | earlier than datetime.date.today() and forces correct group
263 | membership."""
264 | for us in UserSubscription.objects.get(expires__lt=datetime.date.today()):
265 | us.fix()
266 |
267 |
268 | #### Handle PayPal signals
269 | def _ipn_usersubscription(payment):
270 | class PseudoUS(object):
271 | pk = None
272 |
273 | def __nonzero__(self):
274 | return False
275 |
276 | def __init__(self, user, subscription):
277 | self.user = user
278 | self.subscription = subscription
279 |
280 | try:
281 | s = Subscription.objects.get(id=payment.item_number)
282 | except Subscription.DoesNotExist:
283 | s = None
284 |
285 | try:
286 | u = auth.models.User.objects.get(id=payment.custom)
287 | except auth.models.User.DoesNotExist:
288 | u = None
289 |
290 | if u and s:
291 | try:
292 | us = UserSubscription.objects.get(subscription=s, user=u)
293 | except UserSubscription.DoesNotExist:
294 | us = UserSubscription(user=u, subscription=s, active=False)
295 | Transaction(user=u, subscription=s, ipn=payment,
296 | event='new usersubscription', amount=payment.mc_gross
297 | ).save()
298 | else:
299 | us = PseudoUS(user=u, subscription=s)
300 |
301 | return us
302 |
303 |
304 | def handle_payment_was_successful(sender, **kwargs):
305 | us = _ipn_usersubscription(sender)
306 | u, s = us.user, us.subscription
307 | if us:
308 | if not s.recurrence_unit:
309 | if sender.mc_gross == s.price:
310 | us.subscribe()
311 | us.expires = None
312 | us.active = True
313 | us.save()
314 | Transaction(user=u, subscription=s, ipn=sender,
315 | event='one-time payment', amount=sender.mc_gross
316 | ).save()
317 | signals.signed_up.send(s, ipn=sender, subscription=s, user=u,
318 | usersubscription=us)
319 | else:
320 | Transaction(user=u, subscription=s, ipn=sender,
321 | event='incorrect payment', amount=sender.mc_gross
322 | ).save()
323 | signals.event.send(s, ipn=sender, subscription=s, user=u,
324 | usersubscription=us, event='incorrect payment')
325 | else:
326 | if sender.mc_gross == s.price:
327 | us.extend()
328 | us.save()
329 | Transaction(user=u, subscription=s, ipn=sender,
330 | event='subscription payment', amount=sender.mc_gross
331 | ).save()
332 | signals.paid.send(s, ipn=sender, subscription=s, user=u,
333 | usersubscription=us)
334 | else:
335 | Transaction(user=u, subscription=s, ipn=sender,
336 | event='incorrect payment', amount=sender.mc_gross
337 | ).save()
338 | signals.event.send(s, ipn=sender, subscription=s, user=u,
339 | usersubscription=us, event='incorrect payment')
340 | else:
341 | Transaction(user=u, subscription=s, ipn=sender,
342 | event='unexpected payment', amount=sender.mc_gross
343 | ).save()
344 | signals.event.send(s, ipn=sender, subscription=s, user=u, event='unexpected_payment')
345 | ipn.signals.payment_was_successful.connect(handle_payment_was_successful)
346 |
347 |
348 | def handle_payment_was_flagged(sender, **kwargs):
349 | us = _ipn_usersubscription(sender)
350 | u, s = us.user, us.subscription
351 | Transaction(user=u, subscription=s, ipn=sender,
352 | event='payment flagged', amount=sender.mc_gross
353 | ).save()
354 | signals.event.send(s, ipn=sender, subscription=s, user=u, event='flagged')
355 | ipn.signals.payment_was_flagged.connect(handle_payment_was_flagged)
356 |
357 |
358 | def handle_subscription_signup(sender, **kwargs):
359 | us = _ipn_usersubscription(sender)
360 | u, s = us.user, us.subscription
361 | if us:
362 | # deactivate or delete all user's other subscriptions
363 | for old_us in u.usersubscription_set.all():
364 | if old_us == us:
365 | continue # don't touch current subscription
366 | if old_us.cancelled:
367 | old_us.delete()
368 | Transaction(user=u, subscription=s, ipn=sender,
369 | event='remove subscription (deactivated)', amount=sender.mc_gross
370 | ).save()
371 | else:
372 | old_us.active = False
373 | old_us.unsubscribe()
374 | old_us.save()
375 | Transaction(user=u, subscription=s, ipn=sender,
376 | event='deactivated', amount=sender.mc_gross
377 | ).save()
378 |
379 | # activate new subscription
380 | us.subscribe()
381 | us.active = True
382 | us.cancelled = False
383 | us.save()
384 | Transaction(user=u, subscription=s, ipn=sender,
385 | event='activated', amount=sender.mc_gross
386 | ).save()
387 |
388 | signals.subscribed.send(s, ipn=sender, subscription=s, user=u,
389 | usersubscription=us)
390 | else:
391 | Transaction(user=u, subscription=s, ipn=sender,
392 | event='unexpected subscription', amount=sender.mc_gross
393 | ).save()
394 | signals.event.send(s, ipn=sender, subscription=s, user=u,
395 | event='unexpected_subscription')
396 | ipn.signals.subscription_signup.connect(handle_subscription_signup)
397 |
398 |
399 | def handle_subscription_cancel(sender, **kwargs):
400 | us = _ipn_usersubscription(sender)
401 | u, s = us.user, us.subscription
402 | if us.pk is not None:
403 | if not us.active:
404 | us.unsubscribe()
405 | us.delete()
406 | Transaction(user=u, subscription=s, ipn=sender,
407 | event='remove subscription (cancelled)', amount=sender.mc_gross
408 | ).save()
409 | else:
410 | us.cancelled = True
411 | us.save()
412 | Transaction(user=u, subscription=s, ipn=sender,
413 | event='cancel subscription', amount=sender.mc_gross
414 | ).save()
415 | signals.unsubscribed.send(s, ipn=sender, subscription=s, user=u,
416 | usersubscription=us,
417 | # refund=refund, reason='cancel')
418 | reason='cancel')
419 | else:
420 | Transaction(user=u, subscription=s, ipn=sender,
421 | event='unexpected cancel', amount=sender.mc_gross
422 | ).save()
423 | signals.event.send(s, ipn=sender, subscription=s, user=u, event='unexpected_cancel')
424 | ipn.signals.subscription_cancel.connect(handle_subscription_cancel)
425 | ipn.signals.subscription_eot.connect(handle_subscription_cancel)
426 |
427 |
428 | def handle_subscription_modify(sender, **kwargs):
429 | us = _ipn_usersubscription(sender)
430 | u, s = us.user, us.subscription
431 | print 'modify', u, s
432 | if us:
433 | # delete all user's other subscriptions
434 | for old_us in u.usersubscription_set.all():
435 | if old_us == us:
436 | continue # don't touch current subscription
437 | old_us.delete()
438 | Transaction(user=u, subscription=s, ipn=sender,
439 | event='remove subscription (deactivated)', amount=sender.mc_gross
440 | ).save()
441 |
442 | # activate new subscription
443 | us.subscribe()
444 | us.active = True
445 | us.cancelled = False
446 | us.save()
447 | Transaction(user=u, subscription=s, ipn=sender,
448 | event='activated', amount=sender.mc_gross
449 | ).save()
450 |
451 | signals.subscribed.send(s, ipn=sender, subscription=s, user=u,
452 | usersubscription=us)
453 | else:
454 | Transaction(user=u, subscription=u, ipn=sender,
455 | event='unexpected subscription modify', amount=sender.mc_gross
456 | ).save()
457 | signals.event.send(s, ipn=sender, subscription=s, user=u,
458 | event='unexpected_subscription_modify')
459 | ipn.signals.subscription_modify.connect(handle_subscription_modify)
460 |
--------------------------------------------------------------------------------
/subscription/providers.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from django.conf import settings
3 |
4 | """
5 | PROPOSALS (only for payment methods which happens behind the scene)
6 |
7 | 1. New payment methods must be implemented as class (BasePaymentMethod)
8 | 2. According to user selected payment method, build object (Factory DP)
9 | 3. Call proceed() function.
10 |
11 | TODO
12 | 1. Create mappings => to make views more clear, i.e.:
13 |
14 | in urls.py
15 | (r'^(?P\d+)/(?P(standard|pro|authorize))$', 'subscription_detail', {}, 'subscription_detail'),
16 |
17 | in settings.py:
18 | PAYMENT_METHODS_MAPPINGS = {
19 | 'pro': 'WebsitePaymentsPro', #subscription.providers.WebsitePaymentsPro
20 | 'authorize': 'Authorize', #subscription.providers.Authorize
21 | etc..
22 | }
23 |
24 | in.views.py:
25 |
26 | def subscription_details(request, object_id, payment_method="pro"):
27 | from subscription.providers import PaymentMethodFactory, pick_class
28 | payment_object = PaymentMethodFactory(pick_class(payment_method), ...)
29 | payment_object.proceed(...)
30 |
31 | """
32 |
33 | def pick_class(payment_method, default_method):
34 | """
35 | return settings.PAYMENT_METHODS_MAPPINGS.get(payment_method, default_method)
36 | """
37 | pass
38 |
39 |
40 | class BasePaymentMethod(object):
41 | """This class represents the abstract base class for new payment methods"""
42 | def __init__(self):
43 | self.name = None
44 |
45 | def proceed(self):
46 | """Runs payment process"""
47 | pass
48 |
49 | def get_name(self):
50 | """Returns full name of payment method"""
51 | return self.name
52 |
53 |
54 | class PaymentMethodFactory(object):
55 | """Implementation of Factory Design Pattern"""
56 | @staticmethod
57 | def factory(payment_method, **kwargs):
58 | """ Factory method"""
59 | cls = getattr(sys.modules[__name__], payment_method)
60 | return cls(**kwargs)
61 |
62 |
63 | class WebsitePaymentsPro(BasePaymentMethod):
64 | """Wrapper around django-paypal's PayPalPro"""
65 | def __init__(self, **kwargs):
66 | self.name = 'Website Payments Pro'
67 | self.data = kwargs.get('data')
68 | self.request = kwargs.get('request')
69 |
70 | def proceed(self):
71 | from paypal.pro.views import PayPalPro
72 | ppp = PayPalPro(**self.data)
73 | return ppp(self.request)
--------------------------------------------------------------------------------
/subscription/signals.py:
--------------------------------------------------------------------------------
1 | from django.dispatch import Signal
2 |
3 | ## Our signals
4 |
5 | # one time subscriptions
6 | signed_up = Signal()
7 |
8 | # recurring subscriptions
9 | subscribed = Signal()
10 | unsubscribed = Signal()
11 | paid = Signal()
12 |
13 | # misc. subscription-related events
14 | event = Signal()
15 |
16 | # upgrade/downgrade possibility check
17 | change_check = Signal()
18 |
--------------------------------------------------------------------------------
/subscription/tests.py:
--------------------------------------------------------------------------------
1 | from datetime import date
2 | import calendar
3 |
4 | from django.test import TestCase
5 |
6 | import subscription.utils
7 |
8 | A_LEAP_YEAR = 2012
9 | NOT_A_LEAP_YEAR = 2011
10 |
11 | YEARS = (A_LEAP_YEAR, NOT_A_LEAP_YEAR)
12 | MONTHS = xrange(1, 13)
13 |
14 | class SubscriptionUtil(TestCase):
15 |
16 | def test_month(self):
17 | for year in YEARS:
18 | for month in MONTHS:
19 | for day in xrange(1, calendar.monthrange(year, month)[0]+1):
20 | start = date(year, month, day)
21 | try:
22 | added = subscription.utils.extend_date_by(start, 1, 'M')
23 | except ValueError:
24 | raise ValueError("Cannot extend %s by %s months" % (start, 1))
25 |
26 | if month == 12:
27 | self.assertEqual(added.month, 1)
28 | else:
29 | self.assertEqual(added.month, start.month + 1)
30 |
--------------------------------------------------------------------------------
/subscription/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import *
2 |
3 | import django
4 |
5 | if django.VERSION < (1, 5, 0):
6 | import views
7 | urlpatterns = patterns('',
8 | (r'^$', 'subscription.views.subscription_list', {}, 'subscription_list'),
9 | (r'^done/', 'django.views.generic.simple.direct_to_template',
10 | dict(template='subscription/subscription_done.html'), 'subscription_done'),
11 | (r'^change-done/', 'django.views.generic.simple.direct_to_template',
12 | dict(template='subscription/subscription_change_done.html',
13 | extra_context=dict(cancel_url=views.cancel_url)), 'subscription_change_done'),
14 | (r'^cancel/', 'django.views.generic.simple.direct_to_template',
15 | dict(template='subscription/subscription_cancel.html'), 'subscription_cancel'),
16 | )
17 | else:
18 | from django.views.generic import TemplateView
19 | urlpatterns = patterns('subscription.views',
20 | url(r'^$', TemplateView.as_view(template_name='subscription/subscription_list.html'), name='subscription_list'),
21 | url(r'^done/', TemplateView.as_view(template_name='subscription/subscription_done.html'), name='subscription_done'),
22 | url(r'^change-done/', TemplateView.as_view(template_name='subscription/subscription_change_done.html'), name='subscription_change_done'),
23 | url(r'^cancel/', TemplateView.as_view(template_name='subscription/subscription_cancel.html'), name='subscription_cancel'),
24 | )
25 |
26 | urlpatterns += patterns('subscription.views',
27 | (r'^(?P\d+)/$', 'subscription_detail', {}, 'subscription_detail'),
28 | (r'^(?P\d+)/(?P(standard|pro))/$', 'subscription_detail', {}, 'subscription_detail'),
29 | )
30 |
31 | urlpatterns += patterns('',
32 | (r'^paypal/', include('paypal.standard.ipn.urls')),
33 | )
34 |
--------------------------------------------------------------------------------
/subscription/utils.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import calendar
3 |
4 | def extend_date_by(date, amount, unit):
5 | """Extend date `date' by `amount' of time units `unit'.
6 |
7 | `unit' can by 'D' for days, 'W' for weeks, 'M' for months or 'Y'
8 | for years.
9 |
10 | >>> extend_date_by(datetime.date(2007,04,03),5,'Y')
11 | datetime.date(2012, 4, 3)
12 |
13 | >>> extend_date_by(datetime.date(2007,04,03),5,'M')
14 | datetime.date(2007, 9, 3)
15 | >>> extend_date_by(datetime.date(2007,7,3),5,'M')
16 | datetime.date(2007, 12, 3)
17 | >>> extend_date_by(datetime.date(2007,8,3),5,'M')
18 | datetime.date(2008, 1, 3)
19 | >>> subscription.utils.extend_date_by(datetime.date(2007,10,3),5,'M')
20 | datetime.date(2008, 3, 3)
21 |
22 | >>> subscription.utils.extend_date_by(datetime.date(2007,10,3),1,'W')
23 | datetime.date(2007, 10, 10)
24 | >>> subscription.utils.extend_date_by(datetime.date(2007,10,3),2,'W')
25 | datetime.date(2007, 10, 17)
26 | >>> subscription.utils.extend_date_by(datetime.date(2007,10,3),5,'W')
27 | datetime.date(2007, 11, 7)
28 | >>> subscription.utils.extend_date_by(datetime.date(2007,12,3),5,'W')
29 | datetime.date(2008, 1, 7)
30 |
31 | >>> subscription.utils.extend_date_by(datetime.date(2007,10,3),29,'D')
32 | datetime.date(2007, 11, 1)
33 | >>> subscription.utils.extend_date_by(datetime.date(2007,10,7),29,'D')
34 | datetime.date(2007, 11, 5)
35 | >>> subscription.utils.extend_date_by(datetime.date(2007,10,7),99,'D')
36 | datetime.date(2008, 1, 14)
37 | >>> subscription.utils.extend_date_by(datetime.date(2007,12,3),5,'D')
38 | datetime.date(2007, 12, 8)
39 | >>> subscription.utils.extend_date_by(datetime.date(2007,12,30),5,'D')
40 | datetime.date(2008, 1, 4)
41 |
42 | >>> subscription.utils.extend_date_by(datetime.date(2007,10,7),99,'Q')
43 | Traceback (most recent call last):
44 | ...
45 | Unknown unit.
46 | """
47 | if unit == 'D':
48 | return date + datetime.timedelta(1)*amount
49 | elif unit == 'W':
50 | return date + datetime.timedelta(7)*amount
51 | elif unit == 'M':
52 | y, m, d = date.year, date.month, date.day
53 | m += amount
54 | y += m / 12
55 | m %= 12
56 | if not m: m, y = 12, y-1
57 | r = calendar.monthrange(y, m)[1]
58 | if d > r:
59 | d = r
60 | return datetime.date(y, m, d)
61 | elif unit == 'Y':
62 | y, m, d = date.year, date.month, date.day
63 | return datetime.date(y+amount, m, d)
64 | else: raise "Unknown unit."
65 |
--------------------------------------------------------------------------------
/subscription/views.py:
--------------------------------------------------------------------------------
1 | import urllib
2 |
3 | from django.conf import settings
4 | from django.contrib.auth.decorators import login_required
5 | from django.contrib.sites.models import Site
6 | from django.core.urlresolvers import reverse
7 | from django.shortcuts import render, get_object_or_404, redirect
8 | from django.http import Http404
9 | from django.dispatch import Signal
10 |
11 | import django
12 | if django.VERSION <= (1, 5, 0):
13 | from django.views.generic.simple import direct_to_template
14 |
15 | _formclass = getattr(settings, 'SUBSCRIPTION_PAYPAL_FORM', 'paypal.standard.forms.PayPalPaymentsForm')
16 | _formclass_dot = _formclass.rindex('.')
17 | _formclass_module = __import__(_formclass[:_formclass_dot], {}, {}, [''])
18 | PayPalForm = getattr(_formclass_module, _formclass[_formclass_dot + 1:])
19 |
20 | from models import Subscription, UserSubscription
21 |
22 | get_paypal_extra_args = Signal(providing_args=['user', 'subscription', 'extra_args'])
23 |
24 | # http://paypaldeveloper.com/pdn/board/message?board.id=basicpayments&message.id=621
25 | if settings.PAYPAL_TEST:
26 | cancel_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_subscr-find&alias=%s' % urllib.quote(settings.PAYPAL_RECEIVER_EMAIL)
27 | else:
28 | cancel_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=_subscr-find&alias=%s' % urllib.quote(settings.PAYPAL_RECEIVER_EMAIL)
29 |
30 | # Reference document for paypal html variables
31 | # https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_html_Appx_websitestandard_htmlvariables
32 |
33 |
34 | def _paypal_form_args(upgrade_subscription=False, **kwargs):
35 | "Return PayPal form arguments derived from kwargs."
36 | def _url(rel):
37 | if not rel.startswith('/'):
38 | rel = '/' + rel
39 | return 'http://%s%s' % (Site.objects.get_current().domain, rel)
40 |
41 | if upgrade_subscription:
42 | returl = reverse('subscription_change_done')
43 | else:
44 | returl = reverse('subscription_done')
45 |
46 | rv = settings.SUBSCRIPTION_PAYPAL_SETTINGS.copy()
47 | rv.update(notify_url=_url(reverse('paypal-ipn')),
48 | return_url=_url(returl),
49 | cancel_return=_url(reverse("subscription_cancel")),
50 | **kwargs)
51 | return rv
52 |
53 |
54 | def _paypal_form(subscription, user, upgrade_subscription=False, **extra_args):
55 | if not user.is_authenticated:
56 | return None
57 |
58 | if subscription.price <= 0:
59 | # Handles the scenario when subscription price is set to 0 or negative
60 | # value. This means it is a "free plan" and should be handled
61 | # appropriately by user of this library
62 | return None
63 | elif subscription.recurrence_unit:
64 | if subscription.trial_unit == '0':
65 | trial = {}
66 | else:
67 | trial = {
68 | 'a1': 0,
69 | 'p1': subscription.trial_period,
70 | 't1': subscription.trial_unit,
71 | }
72 | kwargs = {}
73 | kwargs.update(trial)
74 | kwargs.update(extra_args)
75 | return PayPalForm(
76 | initial=_paypal_form_args(
77 | cmd='_xclick-subscriptions',
78 | item_name='%s: %s' % (Site.objects.get_current().name, subscription.name),
79 | item_number=subscription.id,
80 | custom=user.id,
81 | a3=subscription.price,
82 | p3=subscription.recurrence_period,
83 | t3=subscription.recurrence_unit,
84 | src=1, # make payments recur
85 | sra=1, # reattempt payment on payment error
86 | upgrade_subscription=upgrade_subscription,
87 | # subscription modification (upgrade/downgrade)
88 | modify=upgrade_subscription and 2 or 0, **kwargs),
89 | button_type='subscribe'
90 | )
91 | else:
92 | return PayPalForm(
93 | initial=_paypal_form_args(
94 | item_name='%s: %s' % (Site.objects.get_current().name, subscription.name),
95 | item_number=subscription.id,
96 | custom=user.id,
97 | amount=subscription.price))
98 |
99 |
100 | def subscription_list(request):
101 | return direct_to_template(
102 | request, template='subscription/subscription_list.html',
103 | extra_context=dict(object_list=Subscription.objects.all()))
104 |
105 |
106 | def subscription_detail(request, object_id, payment_method="standard"):
107 |
108 | FREE_SUBSCRIPTION_URL_NAME = getattr(settings, 'FREE_SUBSCRIPTION_URL_NAME', None)
109 | if FREE_SUBSCRIPTION_URL_NAME:
110 | return redirect(reverse(FREE_SUBSCRIPTION_URL_NAME))
111 |
112 | s = get_object_or_404(Subscription, id=object_id)
113 |
114 | try:
115 | user = request.user.usersubscription_set.get(
116 | active=True)
117 | except UserSubscription.DoesNotExist:
118 | change_denied_reasons = None
119 | user = None
120 | else:
121 | change_denied_reasons = user.try_change(s)
122 |
123 | if change_denied_reasons:
124 | form = None
125 | else:
126 | get_paypal_extra_args.send(sender=None, user=user, subscription=s, extra_args={})
127 | form = _paypal_form(s, request.user, upgrade_subscription=(user is not None) and (user.subscription != s))
128 |
129 | try:
130 | s_us = request.user.usersubscription_set.get(subscription=s)
131 | except UserSubscription.DoesNotExist:
132 | s_us = None
133 |
134 | from subscription.providers import PaymentMethodFactory
135 | # See PROPOSALS section in providers.py
136 | if payment_method == "pro":
137 | domain = Site.objects.get_current().domain
138 | item = {"amt": s.price,
139 | "inv": "inventory", # unique tracking variable paypal
140 | "custom": "tracking", # custom tracking variable for you
141 | "cancelurl": 'http://%s%s' % (domain, reverse('subscription_cancel')), # Express checkout cancel url
142 | "returnurl": 'http://%s%s' % (domain, reverse('subscription_done'))} # Express checkout return url
143 |
144 | data = {"item": item,
145 | "payment_template": "payment.html", # template name for payment
146 | "confirm_template": "confirmation.html", # template name for confirmation
147 | "success_url": reverse('subscription_done')} # redirect location after success
148 |
149 | o = PaymentMethodFactory.factory('WebsitePaymentsPro', data=data, request=request)
150 | # We return o.proceed() just because django-paypal's PayPalPro returns HttpResponse object
151 | return o.proceed()
152 |
153 | elif payment_method == 'standard':
154 | template_vars = {'object': s,
155 | 'usersubscription': s_us,
156 | 'change_denied_reasons': change_denied_reasons,
157 | 'form': form,
158 | 'cancel_url': cancel_url}
159 | template = 'subscription/subscription_detail.html'
160 | return render(request, template, template_vars)
161 | else:
162 | #should never get here
163 | raise Http404
164 |
--------------------------------------------------------------------------------