from . import models
-class PlanAdmin(admin.ModelAdmin):
- list_display = ['min_amount', 'interval']
-
-admin.site.register(models.Plan, PlanAdmin)
+admin.site.register(models.Club)
class PayUOrderInline(admin.TabularInline):
class ScheduleAdmin(admin.ModelAdmin):
- list_display = ['email', 'started_at', 'payed_at', 'expires_at', 'plan', 'amount', 'is_cancelled']
+ list_display = ['email', 'started_at', 'payed_at', 'expires_at', 'amount', 'monthly', 'yearly', 'is_cancelled']
list_search = ['email']
list_filter = ['is_cancelled']
date_hierarchy = 'started_at'
class ScheduleInline(admin.TabularInline):
model = models.Schedule
- fields = ['email', 'plan', 'amount', 'method', 'is_cancelled', 'started_at', 'payed_at', 'expires_at', 'email_sent']
+ fields = ['email', 'amount', 'is_cancelled', 'started_at', 'payed_at', 'expires_at', 'email_sent']
readonly_fields = fields
extra = 0
show_change_link = True
admin.site.register(models.PayUOrder, PayUOrderAdmin)
+
+
+admin.site.register(models.Ambassador)
from decimal import Decimal
from django import forms
from . import models
-from .payment_methods import method_by_slug, methods
from .payu.forms import CardTokenForm
class ScheduleForm(forms.ModelForm):
class Meta:
model = models.Schedule
- fields = ['plan', 'method', 'amount', 'email']
+ fields = ['monthly', 'amount', 'email']
widgets = {
- 'plan': forms.RadioSelect,
- 'method': forms.RadioSelect,
+ 'amount': forms.HiddenInput,
+ 'monthly': forms.HiddenInput,
}
- def __init__(self, *args, request=None, **kwargs):
- super(ScheduleForm, self).__init__(*args, **kwargs)
- self.request = request
- self.plans = models.Plan.objects.all()
- self.payment_methods = methods
- self.fields['amount'].required = False
-
- def clean(self):
- cleaned_data = super(ScheduleForm, self).clean()
-
- if 'plan' in cleaned_data:
- cleaned_data['amount'] = self.fields['amount'].clean(
- self.request.POST['amount-{}'.format(cleaned_data['plan'].id)]
- )
-
- if cleaned_data['amount'] < cleaned_data['plan'].min_amount:
- self.add_error(
- 'amount',
- 'Minimalna kwota dla tego planu to %d zł.' % cleaned_data['plan'].min_amount
- )
-
- if 'method' in cleaned_data:
- method = method_by_slug[cleaned_data['method']]
- if method not in cleaned_data['plan'].payment_methods():
- self.add_error('method', 'Wybrana metoda płatności nie jest dostępna dla tego planu.')
-
+ def clean_amount(self):
+ value = self.cleaned_data['amount']
+ club = models.Club.objects.first()
+ if club and value < club.min_amount:
+ raise forms.ValidationError('Minimalna kwota to %d zł.' % club.min_amount)
+ return value
class PayUCardTokenForm(CardTokenForm):
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-09-30 15:58+0200\n"
-"PO-Revision-Date: 2019-09-30 16:09+0200\n"
+"POT-Creation-Date: 2019-11-28 22:34+0100\n"
+"PO-Revision-Date: 2019-11-28 22:36+0100\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: pl\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n"
"%100<12 || n%100>=14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n"
"%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
-"X-Generator: Poedit 2.0.6\n"
+"X-Generator: Poedit 2.2.4\n"
-#: models.py:24
-msgid "a month"
-msgstr "miesięcznie"
-
-#: models.py:25
-msgid "a year"
-msgstr "raz do roku"
-
-#: models.py:26
-msgid "in perpetuity"
-msgstr "jednorazowo"
-
-#: models.py:29
-msgid "inteval"
-msgstr "okres"
-
-#: models.py:30
-msgid "min amount"
+#: models.py:20
+msgid "minimum amount"
msgstr "minimalna kwota"
-#: models.py:31
-msgid "default amount"
-msgstr "domyślna kwota"
+#: models.py:21
+msgid "minimum amount for year"
+msgstr "minimalna kwota na rok"
-#: models.py:32
-msgid "allow recurring"
-msgstr "płatności cykliczne"
+#: models.py:22
+msgid "proposed amounts for single payment"
+msgstr "proponowane kwoty dla pojedynczej wpłaty"
-#: models.py:33
-msgid "allow one time"
-msgstr "płatności jednorazowe"
+#: models.py:23
+msgid "default single amount"
+msgstr "domyślna kwota dla pojedynczej wpłaty"
-#: models.py:34
-msgid "active"
-msgstr "aktywny"
+#: models.py:24
+msgid "proposed amounts for monthly payments"
+msgstr "proponowane kwoty dla miesięcznych wpłat"
+
+#: models.py:25
+msgid "default monthly amount"
+msgstr "domyślna kwota dla miesięcznych wpłat"
-#: models.py:37 models.py:69
-msgid "plan"
-msgstr "plan"
+#: models.py:28
+msgid "club"
+msgstr "towarzystwo"
-#: models.py:38
-msgid "plans"
-msgstr "plany"
+#: models.py:29
+msgid "clubs"
+msgstr "towarzystwa"
-#: models.py:66
+#: models.py:43
msgid "key"
msgstr "klucz"
-#: models.py:67
+#: models.py:44
msgid "email"
msgstr "email"
-#: models.py:68 models.py:129
+#: models.py:45 models.py:120
msgid "membership"
msgstr "członkostwo"
-#: models.py:70
+#: models.py:46
msgid "amount"
msgstr "kwota"
-#: models.py:71
-msgid "method"
-msgstr "metoda płatności"
+#: models.py:47
+msgid "monthly"
+msgstr "miesięcznie"
+
+#: models.py:48
+msgid "yearly"
+msgstr "rocznie"
-#: models.py:72
+#: models.py:50
msgid "cancelled"
msgstr "anulowany"
-#: models.py:73
+#: models.py:51
msgid "payed at"
msgstr "opłacona"
-#: models.py:74
+#: models.py:52
msgid "started at"
msgstr "start"
-#: models.py:75
+#: models.py:53
msgid "expires_at"
msgstr "wygasa"
-#: models.py:79
+#: models.py:57
msgid "schedule"
msgstr "harmonogram"
-#: models.py:80
+#: models.py:58
msgid "schedules"
msgstr "harmonogramy"
-#: models.py:123
+#: models.py:114
msgid "user"
msgstr "użytkownik"
-#: models.py:124
+#: models.py:115
msgid "created at"
msgstr "utworzone"
-#: models.py:130
+#: models.py:121
msgid "memberships"
msgstr "członkostwa"
-#: models.py:152
+#: models.py:143
msgid "days before"
msgstr "dni przed"
-#: models.py:153
+#: models.py:144
msgid "subject"
msgstr "temat"
-#: models.py:154 payu/models.py:136
+#: models.py:145 payu/models.py:136
msgid "body"
msgstr "treść"
-#: models.py:157
+#: models.py:148
msgid "reminder email"
msgstr "email z przypomnieniem"
-#: models.py:158
+#: models.py:149
msgid "reminder emails"
msgstr "emaile z przypomnieniem"
-#: models.py:163
+#: models.py:154
#, python-format
msgid "a day before expiration"
msgid_plural "%d days before expiration"
msgstr[2] "%d dni przed wygaśnięciem"
msgstr[3] "%d dni przed wygaśnięciem"
-#: models.py:165
+#: models.py:156
#, python-format
msgid "a day after expiration"
msgid_plural "%d days after expiration"
msgstr[2] "%d dni po wygaśnięciu"
msgstr[3] "%d dni przed wygaśnięciem"
-#: models.py:192
+#: models.py:160
+msgid "name"
+msgstr "nazwisko"
+
+#: models.py:161
+msgid "photo"
+msgstr "zdjęcie"
+
+#: models.py:162
+msgid "text"
+msgstr "tekst"
+
+#: models.py:165
+msgid "ambassador"
+msgstr "ambasador"
+
+#: models.py:166
+msgid "ambassadors"
+msgstr "ambasadorowie"
+
+#: models.py:197
msgid "Towarzystwo Wolnych Lektur"
msgstr ""
msgid "PayU notifications"
msgstr "notyfikacje PayU"
+#~ msgid "in perpetuity"
+#~ msgstr "jednorazowo"
+
+#~ msgid "inteval"
+#~ msgstr "okres"
+
+#~ msgid "allow recurring"
+#~ msgstr "płatności cykliczne"
+
+#~ msgid "allow one time"
+#~ msgstr "płatności jednorazowe"
+
+#~ msgid "active"
+#~ msgstr "aktywny"
+
+#~ msgid "plan"
+#~ msgstr "plan"
+
+#~ msgid "plans"
+#~ msgstr "plany"
+
+#~ msgid "method"
+#~ msgstr "metoda płatności"
+
#~ msgid "payment"
#~ msgstr "płatność"
class Command(BaseCommand):
def handle(self, *args, **options):
- for s in Schedule.objects.filter(is_cancelled=False, expires_at__lt=now() + timedelta(1)):
+ for s in Schedule.objects.exclude(monthly=False, yearly=False).filter(is_cancelled=False, expires_at__lt=now() + timedelta(1)):
print(s, s.email, s.expires_at)
s.pay(None)
--- /dev/null
+# Generated by Django 2.2.6 on 2019-11-20 08:26
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('club', '0013_populate_payed_at'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Club',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('min_amount', models.IntegerField(verbose_name='minimum amount')),
+ ('min_for_year', models.IntegerField(verbose_name='minimum amount for year')),
+ ('single_amounts', models.CharField(max_length=255, verbose_name='proposed amounts for single payment')),
+ ('monthly_amounts', models.CharField(max_length=255, verbose_name='proposed amounts for monthly payments')),
+ ],
+ ),
+ ]
--- /dev/null
+# Generated by Django 2.2.6 on 2019-11-27 08:47
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('club', '0014_club'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='schedule',
+ name='monthly',
+ field=models.BooleanField(default=False, verbose_name='monthly'),
+ ),
+ migrations.AddField(
+ model_name='schedule',
+ name='yearly',
+ field=models.BooleanField(default=False, verbose_name='yearly'),
+ ),
+ ]
--- /dev/null
+# Generated by Django 2.2.6 on 2019-11-27 08:49
+
+from django.db import migrations
+
+
+def migrate_plans(apps, schema_editor):
+ Schedule = apps.get_model('club', 'Schedule')
+ schedules = Schedule.objects.filter(method='payu-re')
+ schedules.filter(plan__interval=30).update(monthly=True)
+ schedules.filter(plan__interval=365).update(yearly=True)
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('club', '0015_auto_20191127_0947'),
+ ]
+
+ operations = [
+ migrations.RunPython(
+ migrate_plans,
+ migrations.RunPython.noop,
+ elidable=True,
+ )
+ ]
--- /dev/null
+# Generated by Django 2.2.6 on 2019-11-27 08:59
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('club', '0016_migrate_plans'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='club',
+ name='default_monthly_amount',
+ field=models.IntegerField(default=10, verbose_name='default monthly amount'),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='club',
+ name='default_single_amount',
+ field=models.IntegerField(default=100, verbose_name='default single amount'),
+ preserve_default=False,
+ ),
+ ]
--- /dev/null
+# Generated by Django 2.2.6 on 2019-11-27 09:02
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('club', '0017_auto_20191127_0959'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='schedule',
+ name='plan',
+ ),
+ migrations.DeleteModel(
+ name='Plan',
+ ),
+ ]
--- /dev/null
+# Generated by Django 2.2.6 on 2019-11-27 10:02
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('club', '0018_auto_20191127_1002'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='schedule',
+ name='method',
+ ),
+ ]
--- /dev/null
+# Generated by Django 2.2.6 on 2019-11-27 14:19
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('club', '0019_remove_schedule_method'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Ambassador',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=255)),
+ ('photo', models.ImageField(blank=True, upload_to='')),
+ ],
+ ),
+ migrations.AlterField(
+ model_name='schedule',
+ name='monthly',
+ field=models.BooleanField(default=True, verbose_name='monthly'),
+ ),
+ ]
--- /dev/null
+# Generated by Django 2.2.6 on 2019-11-27 14:45
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('club', '0020_auto_20191127_1519'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='ambassador',
+ options={'ordering': ['name'], 'verbose_name': 'ambassador', 'verbose_name_plural': 'ambassadors'},
+ ),
+ migrations.AlterModelOptions(
+ name='club',
+ options={'verbose_name': 'club', 'verbose_name_plural': 'clubs'},
+ ),
+ migrations.AddField(
+ model_name='ambassador',
+ name='text',
+ field=models.CharField(default='', max_length=1024, verbose_name='text'),
+ preserve_default=False,
+ ),
+ migrations.AlterField(
+ model_name='ambassador',
+ name='name',
+ field=models.CharField(max_length=255, verbose_name='name'),
+ ),
+ migrations.AlterField(
+ model_name='ambassador',
+ name='photo',
+ field=models.ImageField(blank=True, upload_to='', verbose_name='photo'),
+ ),
+ ]
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _, ungettext, ugettext, get_language
from catalogue.utils import get_random_hash
-from .payment_methods import methods, method_by_slug
+from .payment_methods import recurring_payment_method, single_payment_method
from .payu import models as payu_models
+from . import utils
-class Plan(models.Model):
- """ Plans are set up by administrators. """
- MONTH = 30
- YEAR = 365
- PERPETUAL = 999
- intervals = [
- (MONTH, _('a month')),
- (YEAR, _('a year')),
- (PERPETUAL, _('in perpetuity')),
- ]
-
- interval = models.SmallIntegerField(_('inteval'), choices=intervals)
- min_amount = models.DecimalField(_('min amount'), max_digits=10, decimal_places=2)
- default_amount = models.DecimalField(_('default amount'), max_digits=10, decimal_places=2)
- allow_recurring = models.BooleanField(_('allow recurring'))
- allow_one_time = models.BooleanField(_('allow one time'))
- active = models.BooleanField(_('active'), default=True)
+class Club(models.Model):
+ min_amount = models.IntegerField(_('minimum amount'))
+ min_for_year = models.IntegerField(_('minimum amount for year'))
+ single_amounts = models.CharField(_('proposed amounts for single payment'), max_length=255)
+ default_single_amount = models.IntegerField(_('default single amount'))
+ monthly_amounts = models.CharField(_('proposed amounts for monthly payments'), max_length=255)
+ default_monthly_amount = models.IntegerField(_('default monthly amount'))
class Meta:
- verbose_name = _('plan')
- verbose_name_plural = _('plans')
-
+ verbose_name = _('club')
+ verbose_name_plural = _('clubs')
+
def __str__(self):
- return "%s %s" % (self.min_amount, self.get_interval_display())
+ return 'Klub'
- class Meta:
- ordering = ('interval',)
+ def proposed_single_amounts(self):
+ return [int(x) for x in self.single_amounts.split(',')]
- def payment_methods(self):
- for method in methods:
- if self.allow_recurring and method.is_recurring or self.allow_one_time and not method.is_recurring:
- yield method
+ def proposed_monthly_amounts(self):
+ return [int(x) for x in self.monthly_amounts.split(',')]
- def get_next_installment(self, date):
- if self.interval == self.PERPETUAL:
- return datetime.max - timedelta(1)
- elif self.interval == self.YEAR:
- return date.replace(year=date.year + 1)
- elif self.interval == self.MONTH:
- day = date.day
- date = (date.replace(day=1) + timedelta(31)).replace(day=1) + timedelta(day - 1)
- if date.day != day:
- date = date.replace(day=1)
- return date
-
class Schedule(models.Model):
""" Represents someone taking up a plan. """
key = models.CharField(_('key'), max_length=255, unique=True)
email = models.EmailField(_('email'))
membership = models.ForeignKey('Membership', verbose_name=_('membership'), null=True, blank=True, on_delete=models.PROTECT)
- plan = models.ForeignKey(Plan, verbose_name=_('plan'), on_delete=models.PROTECT)
amount = models.DecimalField(_('amount'), max_digits=10, decimal_places=2)
- method = models.CharField(_('method'), max_length=255, choices=[(method.slug, method.name) for method in methods])
+ monthly = models.BooleanField(_('monthly'), default=True)
+ yearly = models.BooleanField(_('yearly'), default=False)
+
is_cancelled = models.BooleanField(_('cancelled'), default=False)
payed_at = models.DateTimeField(_('payed at'), null=True, blank=True)
started_at = models.DateTimeField(_('started at'), auto_now_add=True)
return reverse('club_thanks', args=[self.key])
def get_payment_method(self):
- return method_by_slug[self.method]
+ return recurring_payment_method if self.monthly or self.yearly else single_payment_method
def is_expired(self):
return self.expires_at is not None and self.expires_at <= now()
def is_active(self):
return self.payed_at is not None and (self.expires_at is None or self.expires_at > now())
+ def is_recurring(self):
+ return self.monthly or self.yearly
+
+ def get_next_installment(self, date):
+ if self.yearly:
+ return utils.add_year(date)
+ if self.monthly:
+ return utils.add_month(date)
+ club = Club.objects.first()
+ if club is not None and self.amount >= club.min_for_year:
+ return utils.add_year(date)
+ return utils.add_month(date)
+
def send_email(self):
ctx = {'schedule': self}
send_mail(
return ungettext('a day after expiration', '%d days after expiration', n=-self.days_before)
+class Ambassador(models.Model):
+ name = models.CharField(_('name'), max_length=255)
+ photo = models.ImageField(_('photo'), blank=True)
+ text = models.CharField(_('text'), max_length=1024)
+
+ class Meta:
+ verbose_name = _('ambassador')
+ verbose_name_plural = _('ambassadors')
+ ordering = ['name']
+
+ def __str__(self):
+ return self.name
+
+
########
# #
# PayU #
n = now()
if since is None or since < n:
since = n
- new_exp = self.schedule.plan.get_next_installment(since)
+ new_exp = self.schedule.get_next_installment(since)
if self.schedule.payed_at is None:
self.schedule.payed_at = n
if self.schedule.expires_at is None or self.schedule.expires_at < new_exp:
class PayUNotification(payu_models.Notification):
order = models.ForeignKey(PayUOrder, models.CASCADE, related_name='notification_set')
+
+
return reverse('club_dummy_payment', args=[schedule.key])
-methods = []
-
-pos= getattr(settings, 'CLUB_PAYU_RECURRING_POS', None)
+pos = getattr(settings, 'CLUB_PAYU_RECURRING_POS', None)
if pos:
- payure_method = PayURe(pos)
- methods.append(payure_method)
+ recurring_payment_method = PayURe(pos)
else:
- payure_method = None
+ recurring_payment_method = None
pos = getattr(settings, 'CLUB_PAYU_POS', None)
if pos:
- payu_method = PayU(pos)
- methods.append(payu_method)
+ single_payment_method = PayU(pos)
else:
- payu_method = None
-
-
-#methods.append(PayPal())
-
-
-method_by_slug = {
- m.slug: m
- for m in methods
-}
+ single_payment_method = None
{% extends request.session.from_app|yesno:"base/app.html,base/base.html" %}
{% load chunks %}
-
+{% load thumbnail %}
{% block titleextra %}Towarzystwo Wolnych Lektur{% endblock %}
{% block body %}
-<style>
- .payment-method.disabled {
- opacity: .5;
- filter: grayscale(100%);
- }
-
+ <style>
+ .payment-method.disabled {
+ opacity: .5;
+ filter: grayscale(100%);
+ }
+
+ .button {
+ box-sizing: border-box;
+ display: inline-block;
+ text-align: center;
+ }
+ .kwota, .inna .button, .plan-toggle, .inna input {
+ border: 1px solid black;
+ background: none;
+ cursor: pointer;
+ padding: 10px 0;
+ margin-right: 3%;
+ margin-bottom: 10px;
+ line-height: 3em;
+ }
+ .plan-toggle {
+ width: 46.5%;
+ }
+ .kwota {
+ width: 30%;
+ }
+ .kwota:after {
+ content: " zł";
+ }
+ .kwota.yearly {
+ background: orange;
+
+ }
+ .inna .button {
+ width: 63%;
+ }
+ .inna input {
+ width: 60%;
+ padding-left: 1.5%;
+ padding-right: 1.5%;
+ height: 3em !important;
+ text-align: center;
+ }
+ .chunk-alt {
+ position: relative;
+ overflow: hidden;
+ }
+ .chunk-alt .chunk {
+ top: 0;
+ }
+
+ .kwota.active, .plan-toggle.active {
+ background: black;
+ color: white;
+ }
+ .inna input {display: none;}
+ .inna.active input {display: inline;}
+ .inna.active .button {display: none;}
+
+ .ambassador {
+ display: flex;
+ justify-content: end;
+ padding: 2em;
+ margin-bottom: 1em;
+ border: 1px solid #ddd;
+ }
+ .ambassador img {
+ border-radius: 100%;
+ margin-left: 1em;
+ align-self: center;
+ }
+ .ambassador div {
+ text-align: right;
+ align-self: center;
+ line-height: 1.5em;
+ }
+
+ .club-form-info {
+ line-height: 1.5em;
+ }
+
+ @media screen and (max-width: 1023px) {
+ .club-form-info {
+ margin-top: 2em;
+ }
+ }
+
+
+ @media screen and (min-width: 1024px) {
+ .club-form-info {
+ border-left: 1px solid #ddd;
+ padding-left: 2em;
+ margin-left: 2em;
+ }
+ .twocol {
+ display: flex;
+ }
+ .twocol > form, .twocol > div {
+ flex: 1;
+ }
+ }
+
</style>
-<div class="white-box normal-text">
-
- <h1>{% if membership %}Odnów swoje członkostwo w Towarzystwie Przyjaciół Wolnych Lektur{% else %}Dołącz do Towarzystwa Przyjaciół Wolnych Lektur{% endif %}</h1>
-
-{% chunk 'club_form_top' %}
-
-<form method="POST" action="" id="payment-form" class="wlform">
- {% csrf_token %}
-
- <h2>Zadeklaruj, jak często i jaką kwotą chcesz nas wspierać:</h2>
-
- <ul class="errorlist">
- {% for e in form.non_field_errors %}
- <li>{{ e }}</li>
- {% endfor %}
- {% for e in form.plan.errors %}
- <li>{{ e }}</li>
- {% endfor %}
- {% for e in form.amount.errors %}
- <li>{{ e }}</li>
- {% endfor %}
- </ul>
-
- {% for plan in form.plans %}
-
- <div>
- <input class="plan" type="radio" name="plan" value="{{ plan.id }}" id="plan{{ plan.id }}" data-methods="{% for m in plan.payment_methods %}{{ m.slug }} {% endfor %}">
- <label for="plan{{ plan.id }}">
- <input
- name="amount-{{ plan.id }}"
- type="number"
- placeholder="min. {{ plan.min_amount|floatformat:0 }}"
- value="{{ plan.default_amount|floatformat:0 }}"
- min="{{ plan.min_amount|floatformat:0 }}"
- step="1"
- style="width: 5em;"
- > zł
- {{ plan.get_interval_display }}
- </label>
+ <div class="white-box normal-text">
+
+ <h1>Wspieraj Wolne Lektury</h1>
+ <h2 style="margin-bottom:2em;">
+ {% if membership %}Dziękujemy za Twoje dotychczasowe zaangażowanie! Wesprzyj nas ponownie!{% else %}Dziękujemy, że chcesz razem z nami uwalniać książki!{% endif %}</h2>
+
+ {% chunk 'club_form_top' %}
+ <div class='twocol'>
+
+ <form method="POST" action="" id="payment-form" class="wlform">
+ {% csrf_token %}
+ <h3>Zadeklaruj, jak często i jaką kwotą chcesz nas wspierać:</h3>
+
+ <ul class="errorlist">
+ {% for e in form.non_field_errors %}
+ <li>{{ e }}</li>
+ {% endfor %}
+ {% for e in form.plan.errors %}
+ <li>{{ e }}</li>
+ {% endfor %}
+ {% for e in form.amount.errors %}
+ <li>{{ e }}</li>
+ {% endfor %}
+ </ul>
+
+ {{ form.amount }}
+ {{ form.monthly }}
+ <div>
+ <span class="button plan-toggle" data-plan="plan-single" data-monthly="False">jednorazowo</span>
+ <span class="button plan-toggle active" data-plan="plan-monthly" data-monthly="True">miesięcznie</span>
+ </div>
+
+ <div class="plan" id="plan-single" style="display:none;" data-monthly="False" data-min-for-year="{{ club.min_for_year }}" data-amount="{{ club.default_single_amount }}">
+ {% for amount in club.proposed_single_amounts %}
+ <span class="button kwota{% if amount == club.default_single_amount %} active{% endif %}{% if amount >= club.min_for_year %} yearly{% endif %}">{{ amount }}</span>
+ {% endfor %}
+
+ <span class="inna">
+ <span class="button">inna kwota</span>
+ <input type="number" min="{{ club.min_amount }}">
+ </span>
+ </div>
+
+
+ <div class="plan" id="plan-monthly" data-monthly="True" data-amount="{{ club.default_monthly_amount }}">
+ {% for amount in club.proposed_monthly_amounts %}
+ <span class="button kwota{% if amount == club.default_monthly_amount %} active{% endif %}">{{ amount }}</span>
+ {% endfor %}
+
+ <span class="inna">
+ <span class="button">inna kwota</span>
+ <input type="number" min="{{ club.min_amount }}">
+ </span>
+ </div>
+
+ <h3>
+ Podaj nam swój adres e-mail, żebyśmy mogli się z Tobą skontaktować:
+ </h3>
+
+ <p>
+ {{ form.email }}</p>
+ <button class="submit" type='submit'>Dołącz</button>
+ </form>
+
+ <div class="club-form-info">
+ {% if ambassador %}
+ <div class="ambassador">
+ <div>
+ <em>
+ {{ ambassador.text }}
+ </em>
+ <div style="font-size: 1.2em">{{ ambassador.name }}</div>
+ </div>
+ {% if ambassador.photo %}
+ <img src="{% thumbnail ambassador.photo "100x100" as thumb %}{{ thumb.url }}{% empty %}{{ ambassador.photo.url }}{% endthumbnail %}">
+ {% endif %}
+ </div>
+ {% endif %}
+ <div class="chunk-alt">
+ <div class="chunk chunk-monthly">
+ {% chunk 'club-form-info-monthly' %}
+ </div>
+ <div class="chunk chunk-single" style="display: none;">
+ {% chunk 'club-form-info-single' %}
+ </div>
+ <div class="chunk chunk-single-year" style="display: none;">
+ {% chunk 'club-form-info-single-year' %}
+ </div>
</div>
-
- {% endfor %}
-
- <h2>Wybierz metodę płatności:</h2>
-
- <ul class="errorlist">
- {% for e in form.method.errors %}
- <li>{{ e }}</li>
- {% endfor %}
- </ul>
-
- {% for payment_method in form.payment_methods %}
- <div class="payment-method" id="payment-method-{{ payment_method.slug }}">
- <input type="radio" id="method{{ payment_method.slug }}" name="method" value="{{ payment_method.slug }}">
- <label for="method{{ payment_method.slug }}">
- {% include payment_method.template_name %}
- </label>
- </div>
- {% endfor %}
-
- <h2>
- Podaj nam swój adres e-mail, żebyśmy mogli się z Tobą skontaktować:
- </h2>
-
- <p>
- {{ form.email }}</p>
- <button class="submit" type='submit'>Dołącz</button>
-</form>
-
-{% chunk 'club_form_bottom' %}
+</div>
+</div>
</div>
{% if schedule.expires_at %}
do <strong>{{ schedule.expires_at.date }}</strong>
{% endif %}
-wspierasz nas kwotą {{ schedule.amount }} zł {{ schedule.plan.get_interval_display }}.
+wspierasz nas kwotą {{ schedule.amount }} zł{% if schedule.monthly %} miesięcznie{% endif %}{% if schedule.yearly %} rocznie{% endif %}.
</p>
{% if schedule.is_active %}
- {% if schedule.get_payment_method.is_recurring %}
+ {% if schedule.is_recurring %}
{% if schedule.is_cancelled %}
<p><strong>Płatność anulowana.</strong></p>
<p>
{% block body %}
<div class="white-box normal-text">
- <h1>Płatność cykliczna przez PayU</h1>
+ <h1>Wspierasz Wolne Lektury</h1>
- <p> {{ schedule.email }}</p>
- <p> {{ schedule.amount }}</p>
- <p> {{ schedule.plan.get_interval_display }}</p>
+ <p>Zlecasz comiesięczną płatność w wysokości {{ schedule.amount}} zł. Dziękujemy!</p>
<form id="theform" method='POST'>
{% csrf_token %}
--- /dev/null
+from datetime import timedelta
+
+
+def add_year(date):
+ return date.replace(year=date.year + 1)
+
+def add_month(date):
+ day = date.day
+ date = (date.replace(day=1) + timedelta(31)).replace(day=1) + timedelta(day - 1)
+ if date.day != day:
+ date = date.replace(day=1)
+ return date
+
from .forms import ScheduleForm, PayUCardTokenForm
from . import models
from .helpers import get_active_schedule
-from .payment_methods import payure_method
+from .payment_methods import recurring_payment_method
class ClubView(TemplateView):
else:
return super(JoinView, self).get(request)
- def get_form_kwargs(self):
- kwargs = super().get_form_kwargs()
- kwargs['request'] = self.request
- return kwargs
-
def get_context_data(self, **kwargs):
c = super(JoinView, self).get_context_data(**kwargs)
c['membership'] = getattr(self.request.user, 'membership', None)
c['active_menu_item'] = 'club'
+ c['club'] = models.Club.objects.first()
+
+ c['ambassador'] = models.Ambassador.objects.all().order_by('?').first()
return c
def get_initial(self):
return get_object_or_404(models.Schedule, key=self.kwargs['key'])
def get_pos(self):
- pos_id = payure_method.pos_id
+ pos_id = recurring_payment_method.pos_id
return POSS[pos_id]
def get_success_url(self):
def context_processor(request):
ab = {}
+ overrides = getattr(settings, 'AB_TESTS_OVERRIDES', {})
for abtest, nvalues in settings.AB_TESTS.items():
- print(abtest, nvalues)
- ab[abtest] = hashlib.md5(
+ ab[abtest] = overrides.get(
+ abtest,
+ hashlib.md5(
(abtest + request.META['REMOTE_ADDR']).encode('utf-8')
).digest()[0] % nvalues
- print(ab)
+ )
return {'AB': ab}
p.prev('.read-more-show').removeClass('hide'); // Hide only the preceding "Read More"
e.preventDefault();
});
+
+
+ function update_info() {
+ var amount = parseInt($("#id_amount").val());
+ var monthly = $("#id_monthly").val() == 'True';
+ if (monthly) slug = "monthly";
+ else if (amount >= parseInt($("#plan-single").attr('data-min-for-year'))) slug = 'single-year';
+ else slug = 'single';
+
+ var chunk = $('.club-form-info .chunk-' + slug);
+ if (chunk.css('display') == 'none') {
+ $('.chunk-alt').css('height', $('.chunk-alt').height());
+ $('.chunk-alt .chunk').css('position', 'absolute');
+
+ $('.club-form-info .chunk').fadeOut();
+ $('.club-form-info .chunk.chunk-' + slug).fadeIn(function() {
+ $('.chunk-alt .chunk').css('position', 'static');
+ $('.chunk-alt').css('height', 'auto');
+ });
+ $('.chunk-alt').animate({height: chunk.height()}, 100);
+ }
+ }
+
+ $("#id_amount").val($("#plan-monthly").attr('data-amount'));
+
+ $(".button.kwota").click(function() {
+ var plan = $(this).closest('.plan');
+ $('.kwota', plan).removeClass('active')
+ $('.inna', plan).removeClass('active')
+ $(this).addClass('active');
+
+ var amount = $(this).text();
+ plan.attr("data-amount", amount);
+ $("#id_amount").val(amount);
+
+ update_info();
+ return false;
+ });
+
+ $(".plan-toggle").click(function() {
+ $(".plan-toggle").removeClass('active');
+ $(this).addClass('active')
+ $(".plan").hide();
+ var plan = $("#" + $(this).attr('data-plan'));
+ plan.show();
+ $("#id_amount").val(plan.attr('data-amount'));
+ $("#id_monthly").val(plan.attr('data-monthly'));
+
+ update_info();
+ return false;
+ });
+
+ $(".inna .button").click(function() {
+ var plan = $(this).closest('.plan');
+ $('.kwota', plan).removeClass('active');
+ $(this).parent().addClass('active');
+ $('input', plan).focus();
+
+ var amount = $('input', $(this).parent()).val();
+ plan.attr("data-amount", amount);
+ $("#id_amount").val(amount);
+
+ update_info();
+ return false;
+ });
+
+ $(".inna input").on('input', function() {
+ var plan = $(this).closest('.plan');
+ $('.kwota', plan).removeClass('active');
+ var amount = $(this).val();
+ plan.attr("data-amount", amount);
+ $("#id_amount").val(amount);
+
+ update_info();
+ return false;
+ });
+
});
})(jQuery);