From e8241a8162602206d754266983926890654fa5fa Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Sat, 26 Oct 2019 21:32:02 +0200 Subject: [PATCH 01/16] Fix --- src/catalogue/templates/catalogue/tag_catalogue.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/catalogue/templates/catalogue/tag_catalogue.html b/src/catalogue/templates/catalogue/tag_catalogue.html index 73a5b14f4..4c6a99f43 100644 --- a/src/catalogue/templates/catalogue/tag_catalogue.html +++ b/src/catalogue/templates/catalogue/tag_catalogue.html @@ -12,7 +12,7 @@ {% for tag in best %} - {% cache 86400 catalogue_tag_box rel.pk %} + {% cache 86400 catalogue_tag_box tag.pk %} {% include 'catalogue/tag_box.html' %} {% endcache %} -- 2.20.1 From 3b76a7f9bd1f9dd2e099e76aadc189222beba8f2 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Wed, 13 Nov 2019 08:54:40 +0100 Subject: [PATCH 02/16] Annoy --- src/wolnelektury/static/css/annoy.css | 11 ++++++----- src/wolnelektury/static/js/annoy.js | 8 +++++--- src/wolnelektury/templates/annoy.html | 4 ++-- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/wolnelektury/static/css/annoy.css b/src/wolnelektury/static/css/annoy.css index 1bbf5b32c..651960589 100755 --- a/src/wolnelektury/static/css/annoy.css +++ b/src/wolnelektury/static/css/annoy.css @@ -16,20 +16,20 @@ } #annoy { - font-size: 13px; - line-height: 1.15em; padding: 1em 5em 1em 0; display: none; background: orange; - font-family: Arial, sans-serif; position: relative; z-index: 99; } #annoy p { - margin: 0 0 0 10em;; + margin: 0; } #annoy a { - color: #4E56C8; + color: black; + display: block; + font-size: 2em; + text-align: center; } #annoy .banner { @@ -55,6 +55,7 @@ top: 0; right: 0; color: black; + font-size: 13px; } #annoy a#annoy-off:hover { cursor: pointer; diff --git a/src/wolnelektury/static/js/annoy.js b/src/wolnelektury/static/js/annoy.js index 6e613c713..f2a00f661 100644 --- a/src/wolnelektury/static/js/annoy.js +++ b/src/wolnelektury/static/js/annoy.js @@ -1,22 +1,24 @@ (function($) { $(function() { +var tag = "annoyed2019wspieraj"; + $("#annoy-on").click(function(e) { e.preventDefault(); $("#annoy").slideDown('fast'); $(this).hide(); - if (Modernizr.localstorage) localStorage.removeItem("annoyed2019tpwl"); + if (Modernizr.localstorage) localStorage.removeItem(tag); }); $("#annoy-off").click(function() { $("#annoy").slideUp('fast'); $("#annoy-on").show(); - if (Modernizr.localstorage) localStorage["annoyed2019tpwl"] = true; + if (Modernizr.localstorage) localStorage[tag] = true; }); if (Modernizr.localstorage) { - if (!localStorage["annoyed2019tpwl"]) { + if (!localStorage[tag]) { $("#annoy-on").hide(); $("#annoy").show(); } diff --git a/src/wolnelektury/templates/annoy.html b/src/wolnelektury/templates/annoy.html index 8b2f8e39f..89c596d3d 100644 --- a/src/wolnelektury/templates/annoy.html +++ b/src/wolnelektury/templates/annoy.html @@ -1,5 +1,5 @@ {% load static %} -TPWL +Wesprzyj
@@ -15,7 +15,7 @@

Dowiedz się więcej

- Wolne Lektury potrzebują pomocy! Wesprzyj bezpłatną bibliotekę internetową i przeczytaj utwory napisane specjalnie dla Ciebie. + Wspieraj Wolne Lektury

-- 2.20.1 From f7323c314c42bdc6ff17ec6f3804d6acc9a481ee Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Wed, 13 Nov 2019 09:00:30 +0100 Subject: [PATCH 03/16] Smaller icons. --- src/wolnelektury/static/scss/main/form.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wolnelektury/static/scss/main/form.scss b/src/wolnelektury/static/scss/main/form.scss index ba7616d91..46335a128 100755 --- a/src/wolnelektury/static/scss/main/form.scss +++ b/src/wolnelektury/static/scss/main/form.scss @@ -71,7 +71,7 @@ form table { img { vertical-align: middle; margin-right: 1em; - height: 36px; + height: 30px; } label { display: inline; @@ -82,7 +82,7 @@ form table { .method { border: 1px solid black; border-radius: 5px; - line-height: 34px; + line-height: 28px; display: inline-block; vertical-align: middle; padding: 0 1em; -- 2.20.1 From fc0b706f8c7eb67531df0a2acd972412e47c7010 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Fri, 15 Nov 2019 15:42:40 +0100 Subject: [PATCH 04/16] General A/B testing. --- requirements/requirements.txt | 1 - src/wolnelektury/abtests.py | 13 ++++++++++++ src/wolnelektury/settings/apps.py | 1 - src/wolnelektury/settings/basic.py | 1 + src/wolnelektury/settings/custom.py | 4 ++++ .../templates/piwik/tracking_code.html | 15 +++++++++++++ src/wolnelektury/templatetags/piwik_tags.py | 21 +++++++++++++++++++ 7 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 src/wolnelektury/abtests.py create mode 100644 src/wolnelektury/templates/piwik/tracking_code.html create mode 100644 src/wolnelektury/templatetags/piwik_tags.py diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 08c6882b0..b0c3c8e86 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -40,7 +40,6 @@ pytz django-honeypot==0.7.0 python-memcached -django-piwik python-fb # Feedparser diff --git a/src/wolnelektury/abtests.py b/src/wolnelektury/abtests.py new file mode 100644 index 000000000..7fde579b0 --- /dev/null +++ b/src/wolnelektury/abtests.py @@ -0,0 +1,13 @@ +import hashlib +from django.conf import settings + + +def context_processor(request): + ab = {} + for abtest, nvalues in settings.AB_TESTS.items(): + print(abtest, nvalues) + ab[abtest] = hashlib.md5( + (abtest + request.META['REMOTE_ADDR']).encode('utf-8') + ).digest()[0] % nvalues + print(ab) + return {'AB': ab} diff --git a/src/wolnelektury/settings/apps.py b/src/wolnelektury/settings/apps.py index 5b55b049e..81756be76 100644 --- a/src/wolnelektury/settings/apps.py +++ b/src/wolnelektury/settings/apps.py @@ -53,7 +53,6 @@ INSTALLED_APPS_CONTRIB = [ 'rest_framework', 'fnp_django_pagination', 'pipeline', - 'piwik', 'sorl.thumbnail', 'honeypot', 'fnpdjango', diff --git a/src/wolnelektury/settings/basic.py b/src/wolnelektury/settings/basic.py index 4e21244d8..bef8cd9d1 100644 --- a/src/wolnelektury/settings/basic.py +++ b/src/wolnelektury/settings/basic.py @@ -60,6 +60,7 @@ TEMPLATES = [{ 'django.template.context_processors.media', 'django.template.context_processors.request', 'wolnelektury.context_processors.extra_settings', + 'wolnelektury.abtests.context_processor', 'search.context_processors.search_form', 'machina.core.context_processors.metadata', ), diff --git a/src/wolnelektury/settings/custom.py b/src/wolnelektury/settings/custom.py index 0b8772b95..280f53955 100644 --- a/src/wolnelektury/settings/custom.py +++ b/src/wolnelektury/settings/custom.py @@ -38,3 +38,7 @@ PAYU_POS = { CLUB_PAYU_POS = '300746' CLUB_PAYU_RECURRING_POS = '300746' CLUB_APP_HOST = None + +AB_TESTS = { + 'PAYLOGO': 2, +} diff --git a/src/wolnelektury/templates/piwik/tracking_code.html b/src/wolnelektury/templates/piwik/tracking_code.html new file mode 100644 index 000000000..f09a5c6e5 --- /dev/null +++ b/src/wolnelektury/templates/piwik/tracking_code.html @@ -0,0 +1,15 @@ + + + diff --git a/src/wolnelektury/templatetags/piwik_tags.py b/src/wolnelektury/templatetags/piwik_tags.py new file mode 100644 index 000000000..551b81d31 --- /dev/null +++ b/src/wolnelektury/templatetags/piwik_tags.py @@ -0,0 +1,21 @@ +"""Piwik template tag.""" + +from django import template +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured + + +register = template.Library() + + +@register.inclusion_tag('piwik/tracking_code.html', takes_context=True) +def tracking_code(context): + try: + id = settings.PIWIK_SITE_ID + except AttributeError: + raise ImproperlyConfigured('PIWIK_SITE_ID does not exist.') + try: + url = settings.PIWIK_URL + except AttributeError: + raise ImproperlyConfigured('PIWIK_URL does not exist.') + return {'id': id, 'url': url, 'AB': context.get('AB')} -- 2.20.1 From 0d652be22f4d6e5276e16f493ce58294655d7a0f Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Fri, 15 Nov 2019 15:45:13 +0100 Subject: [PATCH 05/16] Remove hotjar. --- src/catalogue/templates/catalogue/book_detail.html | 7 ------- .../templates/catalogue/tagged_object_list.html | 8 -------- src/wolnelektury/templates/hotjar.html | 11 ----------- src/wolnelektury/templates/main_page.html | 5 ----- 4 files changed, 31 deletions(-) delete mode 100644 src/wolnelektury/templates/hotjar.html diff --git a/src/catalogue/templates/catalogue/book_detail.html b/src/catalogue/templates/catalogue/book_detail.html index eeecbf08a..de1b45a1b 100644 --- a/src/catalogue/templates/catalogue/book_detail.html +++ b/src/catalogue/templates/catalogue/book_detail.html @@ -11,13 +11,6 @@ {% block bodyid %}book-detail{% endblock %} -{% block extrahead %} -{{ block.super }} -{% if request.path == '/katalog/lektura/pan-tadeusz/' %} -{% include "hotjar.html" %} -{% endif %} -{% endblock %} - {% block body %} {% include 'catalogue/book_wide.html' %} diff --git a/src/catalogue/templates/catalogue/tagged_object_list.html b/src/catalogue/templates/catalogue/tagged_object_list.html index e87b32dac..f0a703b4a 100644 --- a/src/catalogue/templates/catalogue/tagged_object_list.html +++ b/src/catalogue/templates/catalogue/tagged_object_list.html @@ -5,14 +5,6 @@ {% block titleextra %}{% if tags %}{% title_from_tags tags %}{% elif list_type == 'gallery' %}{% trans "Gallery" %}{% elif list_type == 'audiobooks' %}{% trans "Audiobooks" %}{% else %}{% trans "Literature" %}{% endif %}{% endblock %} -{% block extrahead %} -{{ block.super }} -{% if request.path == '/katalog/lektury/' or request.path == '/katalog/audiobooki/' %} -{% include "hotjar.html" %} -{% endif %} -{% endblock %} - - {% block bodyid %}tagged-object-list{% endblock %} {% block body %} diff --git a/src/wolnelektury/templates/hotjar.html b/src/wolnelektury/templates/hotjar.html deleted file mode 100644 index 03d8e37ab..000000000 --- a/src/wolnelektury/templates/hotjar.html +++ /dev/null @@ -1,11 +0,0 @@ - - diff --git a/src/wolnelektury/templates/main_page.html b/src/wolnelektury/templates/main_page.html index 49892ecd0..71731b98d 100644 --- a/src/wolnelektury/templates/main_page.html +++ b/src/wolnelektury/templates/main_page.html @@ -11,11 +11,6 @@ {% block title %}{% trans "Wolne Lektury internet library" %}{% endblock %} {% block ogtitle %}{% trans "Wolne Lektury internet library" %}{% endblock %} -{% block extrahead %} -{{ block.super }} -{% include "hotjar.html" %} -{% endblock %} - {% block body %} {% spaceless %} {% carousel 'main' %} -- 2.20.1 From f39c00ef647a7f625d9bd61c570b89d7b9762c61 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Fri, 15 Nov 2019 15:50:56 +0100 Subject: [PATCH 06/16] Word change. --- src/club/locale/pl/LC_MESSAGES/django.mo | Bin 3012 -> 3010 bytes src/club/locale/pl/LC_MESSAGES/django.po | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/club/locale/pl/LC_MESSAGES/django.mo b/src/club/locale/pl/LC_MESSAGES/django.mo index f57a55a51ca48d881e5a85c3ee2e5af1faccbab6..83c0057d7771da1a3f44b1ee2eccda5895bf7c1a 100644 GIT binary patch delta 187 zcmX>ien@=79Hx411_p*BTnr4XK>7ldz6PXufc!g9`X!JS0`k8BX?`Hh3Dm>~q$Png zCy>?v(kwvQ2uOp>F$dBdK-v~aivejjAT180V}Z0ZkS+z%K*TVSn?Vk2(Oe)8q;VCL e-T+mwZL=kFH#>J$YD!*yQDRkodH&>!T%Q5Nvlw3h delta 189 zcmX>kenfo39Hx3+1_p*BTnr4XK>8Atz5%3pfc$$<`ZbUi0`h+VX?`Hh&CS5T2c)He zG$)YO0@5r%+5||0%&`R096;J0NQ(h!4|R_(m=#8nVUflY|(rm52SGo hl->kYuw%0&b2mG0QDT)sUZO%(VtH{@>g4lWp8@`^7}Wp( diff --git a/src/club/locale/pl/LC_MESSAGES/django.po b/src/club/locale/pl/LC_MESSAGES/django.po index 6207cae31..8f9964259 100644 --- a/src/club/locale/pl/LC_MESSAGES/django.po +++ b/src/club/locale/pl/LC_MESSAGES/django.po @@ -30,7 +30,7 @@ msgstr "raz do roku" #: models.py:26 msgid "in perpetuity" -msgstr "raz na zawsze" +msgstr "jednorazowo" #: models.py:29 msgid "inteval" -- 2.20.1 From 97c8c6d7c7961976172bece182832d01c9c0fc4b Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Thu, 28 Nov 2019 22:59:12 +0100 Subject: [PATCH 07/16] Payment overhaul. --- src/club/admin.py | 12 +- src/club/forms.py | 39 +-- src/club/locale/pl/LC_MESSAGES/django.mo | Bin 3010 -> 3317 bytes src/club/locale/pl/LC_MESSAGES/django.po | 162 +++++++---- src/club/management/commands/prolong.py | 2 +- src/club/migrations/0014_club.py | 23 ++ .../migrations/0015_auto_20191127_0947.py | 23 ++ src/club/migrations/0016_migrate_plans.py | 25 ++ .../migrations/0017_auto_20191127_0959.py | 25 ++ .../migrations/0018_auto_20191127_1002.py | 20 ++ .../migrations/0019_remove_schedule_method.py | 17 ++ .../migrations/0020_auto_20191127_1519.py | 26 ++ .../migrations/0021_auto_20191127_1545.py | 37 +++ src/club/models.py | 95 ++++--- src/club/payment_methods.py | 23 +- src/club/templates/club/membership_form.html | 269 +++++++++++++----- src/club/templates/club/schedule.html | 4 +- src/club/templates/payu/rec_payment.html | 6 +- src/club/utils.py | 13 + src/club/views.py | 12 +- src/wolnelektury/abtests.py | 8 +- src/wolnelektury/static/js/base.js | 77 +++++ 22 files changed, 664 insertions(+), 254 deletions(-) create mode 100644 src/club/migrations/0014_club.py create mode 100644 src/club/migrations/0015_auto_20191127_0947.py create mode 100644 src/club/migrations/0016_migrate_plans.py create mode 100644 src/club/migrations/0017_auto_20191127_0959.py create mode 100644 src/club/migrations/0018_auto_20191127_1002.py create mode 100644 src/club/migrations/0019_remove_schedule_method.py create mode 100644 src/club/migrations/0020_auto_20191127_1519.py create mode 100644 src/club/migrations/0021_auto_20191127_1545.py create mode 100644 src/club/utils.py diff --git a/src/club/admin.py b/src/club/admin.py index b0c20c2c5..e0beaeada 100644 --- a/src/club/admin.py +++ b/src/club/admin.py @@ -9,10 +9,7 @@ from modeltranslation.admin import TranslationAdmin 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): @@ -41,7 +38,7 @@ class PayUCardTokenInline(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' @@ -53,7 +50,7 @@ admin.site.register(models.Schedule, ScheduleAdmin) 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 @@ -116,3 +113,6 @@ class PayUOrderAdmin(admin.ModelAdmin): admin.site.register(models.PayUOrder, PayUOrderAdmin) + + +admin.site.register(models.Ambassador) diff --git a/src/club/forms.py b/src/club/forms.py index 1906bc9fe..8df778338 100644 --- a/src/club/forms.py +++ b/src/club/forms.py @@ -4,45 +4,24 @@ 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): diff --git a/src/club/locale/pl/LC_MESSAGES/django.mo b/src/club/locale/pl/LC_MESSAGES/django.mo index 83c0057d7771da1a3f44b1ee2eccda5895bf7c1a..821ce3c27615219f61a0b85ca2af0b7478807086 100644 GIT binary patch literal 3317 zcmb`IO^g&p6vqoe)K&aIL{!8=TwujsW`@Nq0|U`rAR8fUa92H;n40d|-QMo5npAZ& z^n{CK4SF$#$U&D2F;Nf1M2&b6O_YgV2nRzvm}tZ!As!8${J-uVW?+|tiIu7TRlTbB z>b+MV^V`a0rx?n1)U~J=mN2#-442}A^7b;u?grlh?*UJO4}hn^2f?%8L*RLEC3p$! z1FwMhg4e*i!0RB{S#l?1YrsBmE4TqHgD-=m_ZCQc?}5v~(;!sYM`z#E*TA z59ysR_!CI_KNtKRT#f!!@DcD17$v{gfDo|(ko32L6mqrb?*_@v7<7<)8^9kyvUe3Edw+wJ$K^0a{na4Z=?5v!L6H0!2JvGf_|SSU z6&wd)f=z;7Vb5L%NpB@Sq_-BN{oDjT366pkcfFu3I9Ko#NPd3|;>XV7LwP#~PJtIe z0}jDBjei2J06zyw?>i7Kuy!$i5u|v3Dfnk0|Eu72ko;MSNn~#oxEWjrZUXm%bgnc= z_TL04-V;UtUGP5i-!FIuqeZPZORJ8c}LNv7^xr~Ss~wO zey2QEG{^_KC!IpMpz}<3h)M~S{H5|7D&0kjmkRCwQ&}sIaaGYKP|lu4rL#hLphEYc zA9WCwqdtsEcb1BSicr`ysE?!4nH)l0hf0NVLHoBJmCpSZRC-(JtZYNwh)VgTy|18B zPAN}pL?~B=(qkh!YJ@UIYh0w0+!e;-u@0oVwQqBQ(y>3~yCU{=F{M{hOwz`aCZC@* zA(qPX)igUSXQfN4y(;_|0zRb;ceR@GO_bXd+!HAmQ?WE$9&Pw07x#N4w+jdI`E?0H zaxsO4BOozUgI#DsJYcFMPmLAHF8z%MaW0`BDfa_euo#q?VOl+(pI6F4Z zG7s|wnJ1@262?5zDxL`uQEt8?wg^6yH>AF8Xe;WFv0HaWdPyi*r(v0mg(*^RlR6zC z7R#eCODxVAMHi-Q+-Mqb#ysa0WC`c9%KHw>hBmQt&`$fF^I|e>oe2%`_#x-8JmS+N zXB3-S<&{!-hf^*)m0@0~RJT39rBp7J`VI&iI}=7I8;V#PXdV!1IuX;-nUEst=~sCp z>^nGiaBp|<@?fcN1iO!vITL9Ei;&M-8=>&kPVUYKV`W^MoY?0KcjwVsQ_?tlm8ZF3x8xBa;1%E z>1N9E2Z%Ck1q;H&Z85jiKCV@uGw*vkB7a!L4S#O!(}^%TfVkr#Qlvzy>DxfFu~FVV zX@Y6;9H&XJmN!d>MCPxk$_{@PaC-ZIvuAn1)Wt&9o~+{y5qYG%MF%5o=m-ZEzbF^W zh4MUA*yK?+bA2#5>AngHwT;!51ZYGSwn3FS-yz0=b@>Fr@M zW6VYqO$-np9byc4F;No(k+43Z0TT^-(U^GhM7Zch62nPvKEA)9R-iKf{&d%LSN;E0 z-G7dLzh?g0isETQn;;$_&J~P#2A^EUf%aU{n7eTrOZXz*iwAKzMmU16;0k;T2k}EJ z<9QswuTkTChimZ%+-c0b`IVDhbgb;VdE>L#&-ILVKMr$!05$Qf@9!dynpaVYy@8te zZSNUW0v~%XqQ?0amH1EC$Nc6RCwlNVya#WfGX5L22z_Lu|4XO=SD_LNeE$|yWfR^f zP>D?;LzrpQb1z`85bPDAYvwmyPBg%qs7l^NO?VcS$S0@)Kf^EBp3hPD&GL}$%TQZ4 zkL&O(s={x)KYFiv|3D@756)|#B8U5M7?0p)Oz|WR;lHQ~hL}wWt;IDM;A-598t{4l z|9<2VbI^Ol+e9VS_17l`S${3XX*%>0eS{k5E2Kzs88z`0-~Y4s7gSfxdAxUd4 z4W_3|({@}fu$gH_Htrqf^6UM!*qU=`eqdl~xH38ZSS8rC`|--;_R7S<`GLcQWtAPf z7nYaaE6g-PHy2)czuJvMXHwe@YDthL^_H>qtkaG=rn&f<&EjP7Of?KSXn#9fx)EAC=zH5&!@I diff --git a/src/club/locale/pl/LC_MESSAGES/django.po b/src/club/locale/pl/LC_MESSAGES/django.po index 8f9964259..440b7b94e 100644 --- a/src/club/locale/pl/LC_MESSAGES/django.po +++ b/src/club/locale/pl/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" 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" @@ -18,129 +18,121 @@ msgstr "" "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" @@ -149,7 +141,7 @@ msgstr[1] "%d dni przed wygaśnięciem" 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" @@ -158,7 +150,27 @@ msgstr[1] "%d dni po wygaśnięciu" 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 "" @@ -234,6 +246,30 @@ msgstr "notyfikacja PayU" 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ść" diff --git a/src/club/management/commands/prolong.py b/src/club/management/commands/prolong.py index 3c8b36cb1..34ebe63ad 100644 --- a/src/club/management/commands/prolong.py +++ b/src/club/management/commands/prolong.py @@ -9,7 +9,7 @@ from club.models import Schedule 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) diff --git a/src/club/migrations/0014_club.py b/src/club/migrations/0014_club.py new file mode 100644 index 000000000..1183bc3e0 --- /dev/null +++ b/src/club/migrations/0014_club.py @@ -0,0 +1,23 @@ +# 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')), + ], + ), + ] diff --git a/src/club/migrations/0015_auto_20191127_0947.py b/src/club/migrations/0015_auto_20191127_0947.py new file mode 100644 index 000000000..0bd22be37 --- /dev/null +++ b/src/club/migrations/0015_auto_20191127_0947.py @@ -0,0 +1,23 @@ +# 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'), + ), + ] diff --git a/src/club/migrations/0016_migrate_plans.py b/src/club/migrations/0016_migrate_plans.py new file mode 100644 index 000000000..fd2a9f905 --- /dev/null +++ b/src/club/migrations/0016_migrate_plans.py @@ -0,0 +1,25 @@ +# 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, + ) + ] diff --git a/src/club/migrations/0017_auto_20191127_0959.py b/src/club/migrations/0017_auto_20191127_0959.py new file mode 100644 index 000000000..ba2ed0752 --- /dev/null +++ b/src/club/migrations/0017_auto_20191127_0959.py @@ -0,0 +1,25 @@ +# 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, + ), + ] diff --git a/src/club/migrations/0018_auto_20191127_1002.py b/src/club/migrations/0018_auto_20191127_1002.py new file mode 100644 index 000000000..22e150d8e --- /dev/null +++ b/src/club/migrations/0018_auto_20191127_1002.py @@ -0,0 +1,20 @@ +# 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', + ), + ] diff --git a/src/club/migrations/0019_remove_schedule_method.py b/src/club/migrations/0019_remove_schedule_method.py new file mode 100644 index 000000000..6a3283476 --- /dev/null +++ b/src/club/migrations/0019_remove_schedule_method.py @@ -0,0 +1,17 @@ +# 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', + ), + ] diff --git a/src/club/migrations/0020_auto_20191127_1519.py b/src/club/migrations/0020_auto_20191127_1519.py new file mode 100644 index 000000000..1129a3e7a --- /dev/null +++ b/src/club/migrations/0020_auto_20191127_1519.py @@ -0,0 +1,26 @@ +# 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'), + ), + ] diff --git a/src/club/migrations/0021_auto_20191127_1545.py b/src/club/migrations/0021_auto_20191127_1545.py new file mode 100644 index 000000000..e377d461f --- /dev/null +++ b/src/club/migrations/0021_auto_20191127_1545.py @@ -0,0 +1,37 @@ +# 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'), + ), + ] diff --git a/src/club/models.py b/src/club/models.py index 62c1069f2..8c26d03a1 100644 --- a/src/club/models.py +++ b/src/club/models.py @@ -11,64 +11,42 @@ from django import template 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) @@ -100,7 +78,7 @@ class Schedule(models.Model): 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() @@ -108,6 +86,19 @@ class Schedule(models.Model): 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( @@ -165,6 +156,20 @@ class ReminderEmail(models.Model): 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 # @@ -208,7 +213,7 @@ class PayUOrder(payu_models.Order): 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: @@ -225,3 +230,5 @@ class PayUCardToken(payu_models.CardToken): class PayUNotification(payu_models.Notification): order = models.ForeignKey(PayUOrder, models.CASCADE, related_name='notification_set') + + diff --git a/src/club/payment_methods.py b/src/club/payment_methods.py index 99694ef47..7657701ab 100644 --- a/src/club/payment_methods.py +++ b/src/club/payment_methods.py @@ -71,27 +71,14 @@ class PayPal(PaymentMethod): 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 diff --git a/src/club/templates/club/membership_form.html b/src/club/templates/club/membership_form.html index ecba87760..6e64de1d0 100644 --- a/src/club/templates/club/membership_form.html +++ b/src/club/templates/club/membership_form.html @@ -1,91 +1,208 @@ {% extends request.session.from_app|yesno:"base/app.html,base/base.html" %} {% load chunks %} - +{% load thumbnail %} {% block titleextra %}Towarzystwo Wolnych Lektur{% endblock %} {% block body %} - -
- -

{% if membership %}Odnów swoje członkostwo w Towarzystwie Przyjaciół Wolnych Lektur{% else %}Dołącz do Towarzystwa Przyjaciół Wolnych Lektur{% endif %}

- -{% chunk 'club_form_top' %} - -
- {% csrf_token %} - -

Zadeklaruj, jak często i jaką kwotą chcesz nas wspierać:

- -
    - {% for e in form.non_field_errors %} -
  • {{ e }}
  • - {% endfor %} - {% for e in form.plan.errors %} -
  • {{ e }}
  • - {% endfor %} - {% for e in form.amount.errors %} -
  • {{ e }}
  • - {% endfor %} -
- - {% for plan in form.plans %} - -
- - +
+ +

Wspieraj Wolne Lektury

+

+ {% if membership %}Dziękujemy za Twoje dotychczasowe zaangażowanie! Wesprzyj nas ponownie!{% else %}Dziękujemy, że chcesz razem z nami uwalniać książki!{% endif %}

+ + {% chunk 'club_form_top' %} +
+ + + {% csrf_token %} +

Zadeklaruj, jak często i jaką kwotą chcesz nas wspierać:

+ +
    + {% for e in form.non_field_errors %} +
  • {{ e }}
  • + {% endfor %} + {% for e in form.plan.errors %} +
  • {{ e }}
  • + {% endfor %} + {% for e in form.amount.errors %} +
  • {{ e }}
  • + {% endfor %} +
+ + {{ form.amount }} + {{ form.monthly }} +
+ jednorazowo + miesięcznie +
+ + + + +
+ {% for amount in club.proposed_monthly_amounts %} + {{ amount }} + {% endfor %} + + + inna kwota + + +
+ +

+ Podaj nam swój adres e-mail, żebyśmy mogli się z Tobą skontaktować: +

+ +

+ {{ form.email }}

+ + + +
+ {% if ambassador %} +
+
+ + {{ ambassador.text }} + +
{{ ambassador.name }}
+
+ {% if ambassador.photo %} + + {% endif %} +
+ {% endif %} +
+
+ {% chunk 'club-form-info-monthly' %} +
+ +
- - {% endfor %} - -

Wybierz metodę płatności:

- -
    - {% for e in form.method.errors %} -
  • {{ e }}
  • - {% endfor %} -
- - {% for payment_method in form.payment_methods %} -
- - -
- {% endfor %} - -

- Podaj nam swój adres e-mail, żebyśmy mogli się z Tobą skontaktować: -

- -

- {{ form.email }}

- - - -{% chunk 'club_form_bottom' %} +
+
diff --git a/src/club/templates/club/schedule.html b/src/club/templates/club/schedule.html index bb1bf57fb..bfae8c56c 100644 --- a/src/club/templates/club/schedule.html +++ b/src/club/templates/club/schedule.html @@ -15,11 +15,11 @@ Od {{ schedule.started_at.date }} {% if schedule.expires_at %} do {{ schedule.expires_at.date }} {% 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 %}.

{% if schedule.is_active %} - {% if schedule.get_payment_method.is_recurring %} + {% if schedule.is_recurring %} {% if schedule.is_cancelled %}

Płatność anulowana.

diff --git a/src/club/templates/payu/rec_payment.html b/src/club/templates/payu/rec_payment.html index 56425e4f7..254c2bc46 100644 --- a/src/club/templates/payu/rec_payment.html +++ b/src/club/templates/payu/rec_payment.html @@ -7,11 +7,9 @@ {% block body %}

-

Płatność cykliczna przez PayU

+

Wspierasz Wolne Lektury

-

{{ schedule.email }}

-

{{ schedule.amount }}

-

{{ schedule.plan.get_interval_display }}

+

Zlecasz comiesięczną płatność w wysokości {{ schedule.amount}} zł. Dziękujemy!

{% csrf_token %} diff --git a/src/club/utils.py b/src/club/utils.py new file mode 100644 index 000000000..699f5030c --- /dev/null +++ b/src/club/utils.py @@ -0,0 +1,13 @@ +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 + diff --git a/src/club/views.py b/src/club/views.py index e469345fc..32daffde1 100644 --- a/src/club/views.py +++ b/src/club/views.py @@ -14,7 +14,7 @@ from .payu import views as payu_views 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): @@ -48,15 +48,13 @@ class JoinView(CreateView): 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): @@ -132,7 +130,7 @@ class PayURecPayment(payu_views.RecPayment): 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): diff --git a/src/wolnelektury/abtests.py b/src/wolnelektury/abtests.py index 7fde579b0..6dd0ea2fc 100644 --- a/src/wolnelektury/abtests.py +++ b/src/wolnelektury/abtests.py @@ -4,10 +4,12 @@ from django.conf import settings 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} diff --git a/src/wolnelektury/static/js/base.js b/src/wolnelektury/static/js/base.js index 2d0ebbf64..2c2daa5f4 100644 --- a/src/wolnelektury/static/js/base.js +++ b/src/wolnelektury/static/js/base.js @@ -270,6 +270,83 @@ 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); -- 2.20.1 From 7c1d66f69659152dbf468bcf2df8208655ff82c9 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Thu, 28 Nov 2019 23:58:12 +0100 Subject: [PATCH 08/16] Stats fix. --- src/stats/tasks.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/stats/tasks.py b/src/stats/tasks.py index d0aeb7ae5..7c9dc9666 100644 --- a/src/stats/tasks.py +++ b/src/stats/tasks.py @@ -3,9 +3,10 @@ # from celery.task import task from django.conf import settings -from http.client import HTTPConnection import logging from urllib.parse import urlsplit +from urllib.request import urlopen + logger = logging.getLogger(__name__) @@ -22,7 +23,4 @@ except AttributeError: @task(ignore_result=True) def track_request(piwik_args): - piwik_url = "%s%s%s" % (settings.PIWIK_URL, "/piwik.php?", piwik_args) - conn = HTTPConnection(_host) - conn.request('GET', piwik_url) - conn.close() + urlopen("https:%s%s%s" % (settings.PIWIK_URL, "piwik.php?", piwik_args)) -- 2.20.1 From 5232e0f423644e22c6661cd4f2c48c1b54f47a0e Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Fri, 29 Nov 2019 00:23:45 +0100 Subject: [PATCH 09/16] Minor css fix. --- src/club/templates/club/membership_form.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/club/templates/club/membership_form.html b/src/club/templates/club/membership_form.html index 6e64de1d0..1ba7c4b77 100644 --- a/src/club/templates/club/membership_form.html +++ b/src/club/templates/club/membership_form.html @@ -77,6 +77,8 @@ border-radius: 100%; margin-left: 1em; align-self: center; + height: 100px; + width:100px; } .ambassador div { text-align: right; -- 2.20.1 From 3cafbab0ff3cbfee274d9735b89e790babb52c3f Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Fri, 29 Nov 2019 00:30:43 +0100 Subject: [PATCH 10/16] Minor css fix. --- src/club/templates/club/membership_form.html | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/club/templates/club/membership_form.html b/src/club/templates/club/membership_form.html index 1ba7c4b77..ac4e4c141 100644 --- a/src/club/templates/club/membership_form.html +++ b/src/club/templates/club/membership_form.html @@ -67,22 +67,21 @@ .inna.active .button {display: none;} .ambassador { - display: flex; - justify-content: end; - padding: 2em; + padding: 2em 145px 2em 2em; margin-bottom: 1em; border: 1px solid #ddd; + position: relative; } .ambassador img { border-radius: 100%; - margin-left: 1em; - align-self: center; height: 100px; - width:100px; + width: 100px; + position: absolute; + top: 30px; + right: 30px; } .ambassador div { text-align: right; - align-self: center; line-height: 1.5em; } -- 2.20.1 From db3bea808bc78fa2d1c6d0be6e0300003f31059c Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Fri, 29 Nov 2019 00:35:14 +0100 Subject: [PATCH 11/16] Minor css fix. --- src/club/templates/club/membership_form.html | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/club/templates/club/membership_form.html b/src/club/templates/club/membership_form.html index ac4e4c141..def4539a8 100644 --- a/src/club/templates/club/membership_form.html +++ b/src/club/templates/club/membership_form.html @@ -67,17 +67,20 @@ .inna.active .button {display: none;} .ambassador { - padding: 2em 145px 2em 2em; + padding: 2em; margin-bottom: 1em; border: 1px solid #ddd; position: relative; } + .ambassador.with-photo { + padding-right: 145px; + } .ambassador img { border-radius: 100%; height: 100px; width: 100px; position: absolute; - top: 30px; + top: 35px; right: 30px; } .ambassador div { @@ -179,7 +182,7 @@
{% if ambassador %} -
+
{{ ambassador.text }} -- 2.20.1 From f5129e7c47fa5fcf0a20f04249b90afcbc54cb9c Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Fri, 13 Dec 2019 13:31:28 +0100 Subject: [PATCH 12/16] Annoy! --- src/annoy/__init__.py | 0 src/annoy/admin.py | 24 ++ src/annoy/apps.py | 5 + src/annoy/migrations/0001_initial.py | 64 ++++++ .../migrations/0002_auto_20191211_1320.py | 23 ++ .../migrations/0003_auto_20191211_1511.py | 18 ++ .../migrations/0004_auto_20191211_1512.py | 23 ++ .../migrations/0005_auto_20191211_1617.py | 70 ++++++ .../migrations/0006_auto_20191213_1328.py | 23 ++ src/annoy/migrations/__init__.py | 0 src/annoy/models.py | 82 +++++++ src/annoy/places.py | 16 ++ src/annoy/static/annoy/banner.js | 32 +++ src/annoy/static/annoy/banner.scss | 213 ++++++++++++++++++ src/annoy/static/annoy/book_text.js | 51 +++++ src/annoy/static/annoy/book_text.scss | 15 ++ src/annoy/templates/annoy/banner.html | 28 +++ src/annoy/templates/annoy/banners.html | 3 + src/annoy/templatetags/annoy.py | 23 ++ src/annoy/tests.py | 3 + src/annoy/translation.py | 15 ++ src/annoy/utils.py | 9 + src/annoy/views.py | 3 + .../templates/catalogue/book_detail.html | 3 + .../templates/catalogue/book_fragments.html | 4 + .../templates/catalogue/book_text.html | 21 ++ .../templates/catalogue/viewer_base.html | 3 +- src/catalogue/views.py | 3 + src/club/urls.py | 13 +- src/wolnelektury/settings/apps.py | 1 + src/wolnelektury/settings/static.py | 10 +- src/wolnelektury/static/css/annoy.css | 69 ------ src/wolnelektury/static/js/annoy.js | 30 --- src/wolnelektury/static/scss/main/cite.scss | 2 + .../static/scss/main/fragment.scss | 6 +- src/wolnelektury/templates/annoy.html | 25 -- .../templates/base/superbase.html | 4 +- 37 files changed, 800 insertions(+), 137 deletions(-) create mode 100644 src/annoy/__init__.py create mode 100644 src/annoy/admin.py create mode 100644 src/annoy/apps.py create mode 100644 src/annoy/migrations/0001_initial.py create mode 100644 src/annoy/migrations/0002_auto_20191211_1320.py create mode 100644 src/annoy/migrations/0003_auto_20191211_1511.py create mode 100644 src/annoy/migrations/0004_auto_20191211_1512.py create mode 100644 src/annoy/migrations/0005_auto_20191211_1617.py create mode 100644 src/annoy/migrations/0006_auto_20191213_1328.py create mode 100644 src/annoy/migrations/__init__.py create mode 100644 src/annoy/models.py create mode 100644 src/annoy/places.py create mode 100644 src/annoy/static/annoy/banner.js create mode 100644 src/annoy/static/annoy/banner.scss create mode 100644 src/annoy/static/annoy/book_text.js create mode 100644 src/annoy/static/annoy/book_text.scss create mode 100644 src/annoy/templates/annoy/banner.html create mode 100644 src/annoy/templates/annoy/banners.html create mode 100644 src/annoy/templatetags/annoy.py create mode 100644 src/annoy/tests.py create mode 100644 src/annoy/translation.py create mode 100644 src/annoy/utils.py create mode 100644 src/annoy/views.py delete mode 100755 src/wolnelektury/static/css/annoy.css delete mode 100644 src/wolnelektury/static/js/annoy.js delete mode 100644 src/wolnelektury/templates/annoy.html diff --git a/src/annoy/__init__.py b/src/annoy/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/annoy/admin.py b/src/annoy/admin.py new file mode 100644 index 000000000..7a82d6867 --- /dev/null +++ b/src/annoy/admin.py @@ -0,0 +1,24 @@ +from django.contrib import admin +from django import forms +from modeltranslation.admin import TranslationAdmin +from . import models + + +class BannerAdmin(TranslationAdmin): + list_display = ['place', 'text', 'priority', 'since', 'until', 'show_members', 'staff_preview'] + + +admin.site.register(models.Banner, BannerAdmin) + + +class DynamicTextInsertTextInline(admin.TabularInline): + model = models.DynamicTextInsertText + fields = ['text', 'image', 'background_color', 'text_color'] + + +class DynamicTextInsertAdmin(admin.ModelAdmin): + list_display = ['paragraphs'] + inlines = [DynamicTextInsertTextInline] + + +admin.site.register(models.DynamicTextInsert, DynamicTextInsertAdmin) diff --git a/src/annoy/apps.py b/src/annoy/apps.py new file mode 100644 index 000000000..213a4e98e --- /dev/null +++ b/src/annoy/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class AnnoyConfig(AppConfig): + name = 'annoy' diff --git a/src/annoy/migrations/0001_initial.py b/src/annoy/migrations/0001_initial.py new file mode 100644 index 000000000..d5b091ea6 --- /dev/null +++ b/src/annoy/migrations/0001_initial.py @@ -0,0 +1,64 @@ +# Generated by Django 2.2.6 on 2019-12-11 11:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Banner', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('place', models.SlugField(choices=[('top', 'Top of all pages.'), ('book-page', 'Book page'), ('book-text-intermission', 'Book text intermission'), ('fragment-list', 'Next to list of book fragments.')])), + ('text', models.TextField()), + ('text_de', models.TextField(null=True)), + ('text_en', models.TextField(null=True)), + ('text_es', models.TextField(null=True)), + ('text_fr', models.TextField(null=True)), + ('text_it', models.TextField(null=True)), + ('text_lt', models.TextField(null=True)), + ('text_pl', models.TextField(null=True)), + ('text_ru', models.TextField(null=True)), + ('text_uk', models.TextField(null=True)), + ('url', models.CharField(max_length=1024)), + ('priority', models.PositiveSmallIntegerField(default=0)), + ('since', models.DateTimeField(blank=True, null=True)), + ('until', models.DateTimeField(blank=True, null=True)), + ('show_members', models.BooleanField(default=False)), + ], + options={ + 'verbose_name': 'banner', + 'verbose_name_plural': 'banners', + 'ordering': ('place', '-priority'), + }, + ), + migrations.CreateModel( + name='DynamicTextInsert', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('paragraphs', models.IntegerField(verbose_name='pararaphs')), + ('text', models.TextField(verbose_name='text')), + ('text_de', models.TextField(null=True, verbose_name='text')), + ('text_en', models.TextField(null=True, verbose_name='text')), + ('text_es', models.TextField(null=True, verbose_name='text')), + ('text_fr', models.TextField(null=True, verbose_name='text')), + ('text_it', models.TextField(null=True, verbose_name='text')), + ('text_lt', models.TextField(null=True, verbose_name='text')), + ('text_pl', models.TextField(null=True, verbose_name='text')), + ('text_ru', models.TextField(null=True, verbose_name='text')), + ('text_uk', models.TextField(null=True, verbose_name='text')), + ('url', models.CharField(max_length=1024)), + ], + options={ + 'verbose_name': 'dynamic insert', + 'verbose_name_plural': 'dynamic inserts', + 'ordering': ('paragraphs',), + }, + ), + ] diff --git a/src/annoy/migrations/0002_auto_20191211_1320.py b/src/annoy/migrations/0002_auto_20191211_1320.py new file mode 100644 index 000000000..8d23a1f56 --- /dev/null +++ b/src/annoy/migrations/0002_auto_20191211_1320.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.6 on 2019-12-11 12:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('annoy', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='banner', + name='open_label', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AlterField( + model_name='banner', + name='place', + field=models.SlugField(choices=[('top', 'Top of all pages.'), ('book-page', 'Book page'), ('book-text-intermission', 'Book text intermission'), ('book-fragment-list', 'Next to list of book fragments.')]), + ), + ] diff --git a/src/annoy/migrations/0003_auto_20191211_1511.py b/src/annoy/migrations/0003_auto_20191211_1511.py new file mode 100644 index 000000000..daa3f9729 --- /dev/null +++ b/src/annoy/migrations/0003_auto_20191211_1511.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.6 on 2019-12-11 14:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('annoy', '0002_auto_20191211_1320'), + ] + + operations = [ + migrations.AlterField( + model_name='banner', + name='place', + field=models.SlugField(choices=[('top', 'Top of all pages.'), ('book-page', 'Book page'), ('book-text-intermission', 'Book text intermission'), ('book-fragment-list', 'Next to list of book fragments.'), ('blackout', 'Blackout')]), + ), + ] diff --git a/src/annoy/migrations/0004_auto_20191211_1512.py b/src/annoy/migrations/0004_auto_20191211_1512.py new file mode 100644 index 000000000..0e57edf10 --- /dev/null +++ b/src/annoy/migrations/0004_auto_20191211_1512.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.6 on 2019-12-11 14:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('annoy', '0003_auto_20191211_1511'), + ] + + operations = [ + migrations.AddField( + model_name='banner', + name='action_label', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name='banner', + name='close_label', + field=models.CharField(blank=True, max_length=255), + ), + ] diff --git a/src/annoy/migrations/0005_auto_20191211_1617.py b/src/annoy/migrations/0005_auto_20191211_1617.py new file mode 100644 index 000000000..5fa9b95df --- /dev/null +++ b/src/annoy/migrations/0005_auto_20191211_1617.py @@ -0,0 +1,70 @@ +# Generated by Django 2.2.6 on 2019-12-11 15:17 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('annoy', '0004_auto_20191211_1512'), + ] + + operations = [ + migrations.RemoveField( + model_name='dynamictextinsert', + name='text', + ), + migrations.RemoveField( + model_name='dynamictextinsert', + name='text_de', + ), + migrations.RemoveField( + model_name='dynamictextinsert', + name='text_en', + ), + migrations.RemoveField( + model_name='dynamictextinsert', + name='text_es', + ), + migrations.RemoveField( + model_name='dynamictextinsert', + name='text_fr', + ), + migrations.RemoveField( + model_name='dynamictextinsert', + name='text_it', + ), + migrations.RemoveField( + model_name='dynamictextinsert', + name='text_lt', + ), + migrations.RemoveField( + model_name='dynamictextinsert', + name='text_pl', + ), + migrations.RemoveField( + model_name='dynamictextinsert', + name='text_ru', + ), + migrations.RemoveField( + model_name='dynamictextinsert', + name='text_uk', + ), + migrations.AlterField( + model_name='banner', + name='action_label', + field=models.CharField(blank=True, help_text='', max_length=255), + ), + migrations.CreateModel( + name='DynamicTextInsertText', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('background_color', models.CharField(blank=True, max_length=10)), + ('text_color', models.CharField(blank=True, max_length=10)), + ('text', models.TextField(verbose_name='text')), + ('image', models.FileField(blank=True, upload_to='')), + ('insert', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='annoy.DynamicTextInsert')), + ], + ), + ] diff --git a/src/annoy/migrations/0006_auto_20191213_1328.py b/src/annoy/migrations/0006_auto_20191213_1328.py new file mode 100644 index 000000000..acafafeb2 --- /dev/null +++ b/src/annoy/migrations/0006_auto_20191213_1328.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.6 on 2019-12-13 12:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('annoy', '0005_auto_20191211_1617'), + ] + + operations = [ + migrations.AddField( + model_name='banner', + name='staff_preview', + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name='banner', + name='action_label', + field=models.CharField(blank=True, help_text='', max_length=255), + ), + ] diff --git a/src/annoy/migrations/__init__.py b/src/annoy/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/annoy/models.py b/src/annoy/models.py new file mode 100644 index 000000000..26b0136c7 --- /dev/null +++ b/src/annoy/models.py @@ -0,0 +1,82 @@ +from django.apps import apps +from django.conf import settings +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from django.utils.timezone import now +from .places import PLACES, PLACE_CHOICES + + +class Banner(models.Model): + place = models.SlugField(choices=PLACE_CHOICES) + action_label = models.CharField( + max_length=255, blank=True, + help_text=_('') + ) + open_label = models.CharField(max_length=255, blank=True) + close_label = models.CharField(max_length=255, blank=True) + text = models.TextField() + url = models.CharField(max_length=1024) + priority = models.PositiveSmallIntegerField(default=0) + since = models.DateTimeField(null=True, blank=True) + until = models.DateTimeField(null=True, blank=True) + show_members = models.BooleanField(default=False) + staff_preview = models.BooleanField(default=False) + + class Meta: + verbose_name = _('banner') + verbose_name_plural = _('banners') + ordering = ('place', '-priority',) + + def __str__(self): + return self.text + + @classmethod + def choice(cls, place, request): + Membership = apps.get_model('club', 'Membership') + + if hasattr(request, 'annoy_banner_exempt'): + return cls.objects.none() + + if settings.DEBUG: + assert place in PLACES, "Banner place `{}` must be defined in annoy.places.".format(place) + + n = now() + banners = cls.objects.filter( + place=place + ).exclude( + since__gt=n + ).exclude( + until__lt=n + ).order_by('-priority', '?') + + if not request.user.is_staff: + banners = banners.filter(staff_preview=False) + + if request: + if Membership.is_active_for(request.user): + banners = banners.filter(show_members=True) + return banners + + +class DynamicTextInsert(models.Model): + paragraphs = models.IntegerField(_('pararaphs')) + url = models.CharField(max_length=1024) + + class Meta: + verbose_name = _('dynamic insert') + verbose_name_plural = _('dynamic inserts') + ordering = ('paragraphs', ) + + def __str__(self): + return str(self.paragraphs) + + def choose(self): + return self.dynamictextinserttext_set.order_by('?').first() + + +class DynamicTextInsertText(models.Model): + insert = models.ForeignKey(DynamicTextInsert, models.CASCADE) + background_color = models.CharField(max_length=10, blank=True) + text_color = models.CharField(max_length=10, blank=True) + text = models.TextField(_('text')) + image = models.FileField(blank=True) diff --git a/src/annoy/places.py b/src/annoy/places.py new file mode 100644 index 000000000..93b22e44e --- /dev/null +++ b/src/annoy/places.py @@ -0,0 +1,16 @@ +from django.utils.translation import ugettext_lazy as _ + +PLACE_DEFINITIONS = [ + ('top', _('Top of all pages.'), True), + ('book-page', _('Book page'), False), + ('book-text-intermission', _('Book text intermission'), False), + ('book-fragment-list', _('Next to list of book fragments.'), False), + ('blackout', _('Blackout'), True), +] + +PLACE_CHOICES = [p[:2] for p in PLACE_DEFINITIONS] + +PLACES = { + p[0]: p[2] + for p in PLACE_DEFINITIONS +} diff --git a/src/annoy/static/annoy/banner.js b/src/annoy/static/annoy/banner.js new file mode 100644 index 000000000..ee4561341 --- /dev/null +++ b/src/annoy/static/annoy/banner.js @@ -0,0 +1,32 @@ +(function($) { + $(function() { + + $(".annoy-banner-on").each(function() { + var $on = $(this); + var tag = 'annoyed' + $on.attr('data-target'); + var $target = $($on.attr('data-target')); + var $off = $('.annoy-banner-off', $target); + + $on.click(function(e) { + e.preventDefault(); + $target.slideDown('fast'); + $on.hide(); + if (Modernizr.localstorage) localStorage.removeItem(tag); + }); + + $off.click(function() { + $target.slideUp('fast'); + $on.show(); + if (Modernizr.localstorage) localStorage[tag] = true; + }); + + if (Modernizr.localstorage) { + if (!localStorage[tag]) { + $on.hide(); + $target.show(); + } + } + }); + + }); +})(jQuery); diff --git a/src/annoy/static/annoy/banner.scss b/src/annoy/static/annoy/banner.scss new file mode 100644 index 000000000..33fd48199 --- /dev/null +++ b/src/annoy/static/annoy/banner.scss @@ -0,0 +1,213 @@ +.annoy-banner { + background: orange; + + p { + text-align: center; + } + + a { + color: black; + display: block; + padding: 1em 2em; + } +} + + +.annoy-banner-off { + cursor: pointer; +} + +.annoy-banner_top { + display: none; + + p { + margin: 0; + font-size: 2em; + } + + .annoy-banner-off { + padding: .5em 1em .5em; + width: 1em; + text-align:center; + font-family: Arial, sans-serif; + display: block; + + border-radius: 0 0 0 1em; + position: absolute; + top: 0; + right: 0; + color: black; + font-size: 13px; + } +} + +.annoy-banner-on_top { + font-size: 13px; + line-height: 1.15em; + + background: orange; + z-index: 99; + font-family: Arial, sans-serif; + display: block; + padding: .1em 1em; + text-align:center; + border-radius: 0 0 0 1em; + position: absolute; + top: 0; + right: 0; + color: black; +} + + +.annoy-banner_book-page { + margin-top: 30px; + font-size: 2em; + + p { + margin: 0; + } +} + + +.annoy-banner_book-fragment-list { + margin-right: 2em; +} + + + +.annoy-banner_blackout { + display: none; + + position: fixed; + z-index: 100000; + left: 0; + right: 0; + top: 0; + bottom: 0; + background: black; + color: white; + font-size: 16pt; + line-height: 26pt; + padding: 0; + + .annoy-banner-inner { + padding: 0 40% 0 15%; + height: 100%; + overflow-y: scroll; + padding-top: 20vh; + padding-bottom: 2em; + width: 101vw; + box-sizing: border-box; + } + + p { + text-align: left; + } + + a { + color: white; + position: absolute; + right: 10%; + width: 25%; + border-radius: 24px; + text-align: center; + box-sizing: border-box; + padding: 1em; + + &.annoy-banner-off { + bottom: 20vh; + background: #666; + border-radius: auto 100%; + font-size: .75em; + padding: .5em; + width: 20%; + margin-right: 2.5%; + } + + &.action { + bottom: 33vh; + background: #018189; + color: white; + border-radius: auto 100%; + padding: 1.5em 1em; + } + } + + @media screen and (max-height: 1000px) { + .annoy-banner-inner { + padding-top: 5vh; + } + } + + @media screen and (max-width: 1280px) { + .annoy-banner-inner { + padding-left: 5%; + padding-right: 50%; + padding-left: 5%; + padding-right: 50%; + } + + a { + right: 5%; + width: 40%; + + &.annoy-banner-off { + width: 40%; + margin-right: 0%; + } + } + + } + @media screen and (max-width: 1024px) { + font-size: 14pt; + line-height: 21pt; + } + @media screen and (max-height: 820px) { + .annoy-banner-inner { + padding-top: 5vh; + } + } + @media screen and (max-width: 820px) and (max-height: 820px) { + a.action { + bottom: 20vh; + } + a.annoy-banner-off { + bottom: 5vh; + } + } + @media screen and (max-height: 400px) { + a.action { + bottom: 30vh; + } + } + + +} +.annoy-banner-on_blackout { + position: fixed; + z-index: 9999; + bottom: 0; + right: 0; + background: black; + color: white; + border-radius: 1em 0 0 0; + padding: .1em 1em; +} + + + +.dynamic-insert.with-image { + min-height: 120px; + padding: 0; + + a { + position: relative; + .text { + } + img { + position: absolute; + top: 10px; + right: 10px; + } + } +} diff --git a/src/annoy/static/annoy/book_text.js b/src/annoy/static/annoy/book_text.js new file mode 100644 index 000000000..267d6e1bd --- /dev/null +++ b/src/annoy/static/annoy/book_text.js @@ -0,0 +1,51 @@ +(function($) { + $(function() { + + + var fold = $(window).scrollTop() + $(window).height(); + + var inserts = []; + $("#annoy-stubs .dynamic-insert").each(function() {inserts.push($(this));}); + + var $intermissions = $("#annoy-stubs .annoy-banner_book-text-intermission"); + if ($intermissions.length) { + var which = 0; + $("#book-text a + h2").each(function(i, e) { + console.log(i); + if (i) { + $($intermissions[which]).clone().insertBefore($(this)).show(); + which = (which + 1) % $intermissions.length; + } + }); + + if ($("#footnotes").length) { + $($intermissions[which]).clone().insertBefore($("#footnotes")).show(); + } else { + $($intermissions[which]).clone().appendTo($("#book-text")).show(); + } + }; + + if (inserts) { + var underFold = false; + var counter = 0; + $(".paragraph, .stanza").each(function() { + var p = $(this); + if (!underFold) { + if (p.offset().top > fold) { + underFold = true; + } + } + if (underFold) { + if (inserts[0].attr('data-paragraphs') == counter) { + insert = inserts.shift(); + insert.insertBefore(p); + } + counter += 1; + } + return inserts.length > 0; + }); + }; + + + }); +})(jQuery); diff --git a/src/annoy/static/annoy/book_text.scss b/src/annoy/static/annoy/book_text.scss new file mode 100644 index 000000000..37c83ef70 --- /dev/null +++ b/src/annoy/static/annoy/book_text.scss @@ -0,0 +1,15 @@ +.dynamic-insert, .intermission { + margin: 2em 0; + padding: 2em; + background: orange; + + a { + display: block; + color: black; + } +} + + +#annoy-stubs { + display: none; +} diff --git a/src/annoy/templates/annoy/banner.html b/src/annoy/templates/annoy/banner.html new file mode 100644 index 000000000..3f54eb26e --- /dev/null +++ b/src/annoy/templates/annoy/banner.html @@ -0,0 +1,28 @@ +{% if banner %} + {% if closable %} + + {{ banner.open_label }} + + {% endif %} +
+
+ {% if banner.action_label %} + {{ banner.text|safe|linebreaks }} + + {{ banner.action_label }} + + {% else %} + + {{ banner.text|safe|linebreaks }} + + {% endif %} + {% if closable %} + {{ banner.close_label|default:"x" }} + {% endif %} +
+
+ +{% endif %} diff --git a/src/annoy/templates/annoy/banners.html b/src/annoy/templates/annoy/banners.html new file mode 100644 index 000000000..7de539e0d --- /dev/null +++ b/src/annoy/templates/annoy/banners.html @@ -0,0 +1,3 @@ +{% for banner in banners %} + {% include "annoy/banner.html" %} +{% endfor %} diff --git a/src/annoy/templatetags/annoy.py b/src/annoy/templatetags/annoy.py new file mode 100644 index 000000000..21f1bb85e --- /dev/null +++ b/src/annoy/templatetags/annoy.py @@ -0,0 +1,23 @@ +from django import template +from ..models import Banner +from ..places import PLACES + + +register = template.Library() + + +@register.inclusion_tag('annoy/banner.html', takes_context=True) +def annoy_banner(context, place): + banners = Banner.choice(place, request=context['request']) + return { + 'banner': banners.first(), + 'closable': PLACES.get(place, False), + } + + +@register.inclusion_tag('annoy/banners.html', takes_context=True) +def annoy_banners(context, place): + return { + 'banners': Banner.choice(place, request=context['request']), + 'closable': PLACES.get(place, False), + } diff --git a/src/annoy/tests.py b/src/annoy/tests.py new file mode 100644 index 000000000..7ce503c2d --- /dev/null +++ b/src/annoy/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/annoy/translation.py b/src/annoy/translation.py new file mode 100644 index 000000000..eb17ada17 --- /dev/null +++ b/src/annoy/translation.py @@ -0,0 +1,15 @@ +# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from modeltranslation.translator import translator, TranslationOptions +from . import models + + +class BannerTranslationOptions(TranslationOptions): + fields = ('text',) + + + +translator.register(models.Banner, BannerTranslationOptions) + + diff --git a/src/annoy/utils.py b/src/annoy/utils.py new file mode 100644 index 000000000..07a1a63cc --- /dev/null +++ b/src/annoy/utils.py @@ -0,0 +1,9 @@ +from functools import wraps + + +def banner_exempt(view): + @wraps(view) + def wrapped(request, *args, **kwargs): + request.annoy_banner_exempt = True + return view(request, *args, **kwargs) + return wrapped diff --git a/src/annoy/views.py b/src/annoy/views.py new file mode 100644 index 000000000..91ea44a21 --- /dev/null +++ b/src/annoy/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/src/catalogue/templates/catalogue/book_detail.html b/src/catalogue/templates/catalogue/book_detail.html index de1b45a1b..6e875bd4e 100644 --- a/src/catalogue/templates/catalogue/book_detail.html +++ b/src/catalogue/templates/catalogue/book_detail.html @@ -2,6 +2,7 @@ {% load i18n %} {% load catalogue_tags %} {% load build_absolute_uri from fnp_common %} +{% load annoy_banner from annoy %} {% load cache %} {% block titleextra %}{{ book.pretty_title }}{% endblock %} @@ -27,6 +28,8 @@ {% endif %} + {% annoy_banner 'book-page' %} +

{% trans "See also" %}:

{% related_books book taken=book.other_versions|length %} diff --git a/src/catalogue/templates/catalogue/book_fragments.html b/src/catalogue/templates/catalogue/book_fragments.html index 7696c149f..4bcb30e01 100644 --- a/src/catalogue/templates/catalogue/book_fragments.html +++ b/src/catalogue/templates/catalogue/book_fragments.html @@ -1,6 +1,7 @@ {% extends "base/base.html" %} {% load i18n %} {% load work_list from catalogue_tags %} +{% load annoy_banner from annoy %} {% block titleextra %}{% trans "Theme" %} {{ theme }} {% trans "in work " %} {{ book }}{% endblock %} @@ -12,6 +13,9 @@ {{ theme }}
{% trans "in work " %} {{ book }} + + {% annoy_banner 'book-fragment-list' %} +
diff --git a/src/catalogue/templates/catalogue/book_text.html b/src/catalogue/templates/catalogue/book_text.html index a1c0b23f9..c08f76cff 100644 --- a/src/catalogue/templates/catalogue/book_text.html +++ b/src/catalogue/templates/catalogue/book_text.html @@ -1,7 +1,9 @@ {% extends "catalogue/viewer_base.html" %} {% load i18n %} {% load catalogue_tags %} +{% load chunks %} {% load thumbnail %} +{% load annoy_banners from annoy %} {% block title %}{{ book.pretty_title }}{% endblock %} @@ -109,4 +111,23 @@
{% include 'catalogue/book_short.html' %}
+ +
+ {% annoy_banners 'book-text-intermission' %} + + {% for insert in inserts %} + {% with text=insert.choose %} + + {% endwith %} + {% endfor %} +
{% endblock footer %} diff --git a/src/catalogue/templates/catalogue/viewer_base.html b/src/catalogue/templates/catalogue/viewer_base.html index a975d67af..6df862c16 100644 --- a/src/catalogue/templates/catalogue/viewer_base.html +++ b/src/catalogue/templates/catalogue/viewer_base.html @@ -3,6 +3,7 @@ {% load static from static %} {% load pipeline %} {% load piwik_tags %} +{% load annoy_banner from annoy %} @@ -13,7 +14,7 @@ {% block extrahead %}{% endblock %} - {% include "annoy.html" %} + {% annoy_banner 'top' %}