From 28cb104054903726b0556222929f8f2e9941882d Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Wed, 27 Mar 2013 17:11:51 +0100 Subject: [PATCH 1/1] Basically usable funding workflow. --- apps/funding/forms.py | 27 +- apps/funding/locale/pl/LC_MESSAGES/django.mo | Bin 0 -> 4062 bytes apps/funding/locale/pl/LC_MESSAGES/django.po | 265 ++++++++++++++++++ .../0003_auto__add_field_offer_due.py | 89 ++++++ .../0004_auto__add_field_funding_anonymous.py | 90 ++++++ apps/funding/models.py | 19 +- apps/funding/static/funding/funding.css | 28 +- apps/funding/static/funding/funding.scss | 21 +- .../templates/funding/offer_detail.html | 69 +++-- .../funding/templates/funding/offer_list.html | 24 +- .../templates/funding/tags/funding.html | 28 +- .../funding/tags/offer_detail_head.html | 35 +++ apps/funding/templates/funding/thanks.html | 9 +- .../templates/funding/widgets/amount.html | 17 ++ apps/funding/templates/funding/wlfund.html | 26 +- apps/funding/templatetags/funding_tags.py | 12 + apps/funding/views.py | 8 +- apps/funding/widgets.py | 41 +++ apps/wolnelektury_core/static/css/form.css | 1 + apps/wolnelektury_core/static/css/form.scss | 26 ++ lib/pyscss_compiler.py | 19 ++ requirements.txt | 3 +- wolnelektury/settings/custom.py | 2 + wolnelektury/settings/static.py | 5 +- 24 files changed, 751 insertions(+), 113 deletions(-) create mode 100644 apps/funding/locale/pl/LC_MESSAGES/django.mo create mode 100644 apps/funding/locale/pl/LC_MESSAGES/django.po create mode 100644 apps/funding/migrations/0003_auto__add_field_offer_due.py create mode 100644 apps/funding/migrations/0004_auto__add_field_funding_anonymous.py create mode 100644 apps/funding/templates/funding/tags/offer_detail_head.html create mode 100644 apps/funding/templates/funding/widgets/amount.html create mode 100644 apps/funding/widgets.py create mode 100644 apps/wolnelektury_core/static/css/form.css create mode 100644 apps/wolnelektury_core/static/css/form.scss create mode 100644 lib/pyscss_compiler.py diff --git a/apps/funding/forms.py b/apps/funding/forms.py index eb75fb6f1..6b2316e80 100755 --- a/apps/funding/forms.py +++ b/apps/funding/forms.py @@ -1,30 +1,41 @@ from django import forms +from django.utils.translation import ugettext_lazy as _, ugettext as __ from .models import Offer +from .widgets import PerksAmountWidget class DummyForm(forms.Form): - amount = forms.DecimalField() - name = forms.CharField() - email = forms.EmailField() + required_css_class = 'required' + + name = forms.CharField(label=_("Name")) + anonymous = forms.BooleanField(label=_("Anonymously"), + required=False, + help_text=_("Check if you don't want your name to be visible publicly.")) + email = forms.EmailField(label=_("E-mail"), + help_text=_("Won't be publicised.")) + amount = forms.DecimalField(label=_("Amount"), decimal_places=2, + widget=PerksAmountWidget()) def __init__(self, offer, *args, **kwargs): self.offer = offer super(DummyForm, self).__init__(*args, **kwargs) + self.fields['amount'].widget.form_instance = self def clean_amount(self): if self.cleaned_data['amount'] <= 0: - raise forms.ValidationError("A!") + raise forms.ValidationError(__("Enter positive amount.")) return self.cleaned_data['amount'] def clean(self): - if self.offer != Offer.current(): - raise forms.ValidationError("B!") + if not self.offer.is_current(): + raise forms.ValidationError(__("This offer is out of date.")) return self.cleaned_data def save(self): - print self.cleaned_data return self.offer.fund( name=self.cleaned_data['name'], email=self.cleaned_data['email'], - amount=self.cleaned_data['amount']) + amount=self.cleaned_data['amount'], + anonymous=self.cleaned_data['anonymous'], + ) diff --git a/apps/funding/locale/pl/LC_MESSAGES/django.mo b/apps/funding/locale/pl/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..537a35fb8e89c92f3be830d7c25265c5656f7cb8 GIT binary patch literal 4062 zcmb`JUu+yl9mjvrP>2g{36Mf5WgP0%Cb`R<(>CF1hdQp4IV#{4Ep-Q3)jA0rCCq?wv3H z%1c)_``P*X`#UrDn*;m4MR-1g@({`&-bJ(me)lfC@celn(cR!*!2RIg!F#~}fL{Xd zeh<-m!8(X0ia=h=fnNl_2lD)T@Z$9kfC4-Q9s*avRnUQ-0Cz!N=Xc=y!8gGBz<+=b zfcqef27VZP9ef7-CkPRA>V3JL z3n155iuxsx?fx>zt_eYyMxCO51;i4Rg7<=3Ab#`>yjahdK$dqMWc&Ic&;KdNa()5g zN58_0`(FpS|BnU#3bH@mD*FFhwBLoG@cjKCkG~&eIR`;r=LmQmF|8E+zlD={{2L&e z=x<;X{8!N~VI;RV!Ly(RzW}}h^1KJ2#HYZA!NcHbkmIJo=fD?0j_aSm`@#Jm$m8-L z$nl#11vm@B1bPlU2sS|0`>P=P@tYvWZ3o0s^a{x9{scS+z6tWY$KmV;!6on+umQ3k zu7hmH4~qJaKwkI9Ap7O#Am`05LALAn1>XdD+*=^)^Y5a5F9v@S^@AYm^#sUrHbA!H z^96m7^=*T^PFl2Yfh_k$5I_2QQU5l`dVU9lDEdCg`uq^&Jne%l=hq;@nC+oQK+a`; zrcro&8RZF-PooST_BZRy`3HAp53h+(X3t5y*>|H4?;VJuNt6$ccHp~G)F(jpO$p@$ z3g-jc#`eA&h2t{%97lzH#&+=8P#i7vIKDY|*lvCvMVZRJ^4CWJ&N24y;NcwLygFJm z904CgLHg2%P}uhZ6LF>+`7_7l zsWP3Gp^Q95r;Q0{zNx%G=(#ay)_l5L1IEcl9cdNWj_)7=cU7bazz zH({u}7@E|vv=dDUZ8w#Q-Vr4iN^Pk_Vy$)55V5ok6+?#0MYL5se!I@d#KQq)It$e$ z%*$ORysothwz0leYNu?O7VGTA!TQ<>tUUDTW+EmgRpd{(qZozGO}K0ri_8_S2?u23 z{H~gEWm?YSRkx-sI?~2kh9mOm0z679kZ6U>b6}Mnny-Sz%XCpe8)!7LI@)V=vD@hw z8xJUMi0-geW-1OxE9+>zDWf0@Z{65Dt}b6|e6No`5xqN^SEI{K1UAb;%^n_654pqo z=6MPqWuEoRbc2Ff`k{&QYcShG1TgM%NIXwQqKUDUN87MHkKW` z8p;?(we!3#94d#KN_OL>vE&UBm3-wqt2?na5g}ZpvK&KziBkf3HlXlA1LX!)N4a&b z!|4OXayQ@H%I2qr>c|91B4g?(8)PrES&c1-eJlg7CB??t61lM3Acstk zDNlEbbsQ?fNufuRNU*{^#Di9?X<-YN7X8vCgc@em#OmDq`MI+T;?ly}#l@8iV{3}j zrnKxD+Ap2%He6}l)Wq28%6e(uD#^YmJuPEZ6VsLI@lxe@Y5Ihyo~TtTM=I4y1p`ZK zig*A28Mh=|Tw3Sj2|+QoCf1~{0x_S;Rv^WU)Lvd(Uf8p>TCR-EndnT6IVXxZ- zOwZ6|m#Q$(*i|Mq$bkNq68#-({6LGWA=z~R4W^YyX&%j?PbjNjgYt0Is$^cJT-FZp(IkBVU6 z(IQ?@!({#m46akjk|rjL8)8+f2>$m|B~sy~yW9O8Pn+I|3bfc}BN2|m^|Tma5jIlN z%ILa7xd?&5U6+oEWxo`9?QY`;R}MLpw2*Ect*mU_bcYQ3Z=BTWZ^<~Jr&DMYbX%&O zh*TDt(Q~-sJgvq@yT|v^>yFN#^IG}IP>{U+z+qP2>i(e%%h|txTk}23gnaMZ7z;#y z+4NuK3`fQ*A9f*)tn@h1Z*$uW?ij4HmYvo~3)zxyr#v8h;pjH+V_Z16Z?fEsa0hg> zBVWcc-zkCgTAEb;zb3#G8>hU#GV?_P^lK$-bSZBOCimJ}y( Ux4%V5^~?qsQ|u_h(dH-gKf((|Q~&?~ literal 0 HcmV?d00001 diff --git a/apps/funding/locale/pl/LC_MESSAGES/django.po b/apps/funding/locale/pl/LC_MESSAGES/django.po new file mode 100644 index 000000000..3e3e62aa0 --- /dev/null +++ b/apps/funding/locale/pl/LC_MESSAGES/django.po @@ -0,0 +1,265 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-03-27 17:10+0100\n" +"PO-Revision-Date: 2013-03-27 17:10+0100\n" +"Last-Translator: Radek Czajka \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" + +#: forms.py:10 +msgid "Name" +msgstr "Imię i nazwisko" + +#: forms.py:11 +msgid "Anonymously" +msgstr "Anonimowo" + +#: forms.py:13 +msgid "Check if you don't want your name to be visible publicly." +msgstr "Zaznacz, jeśli nie chcesz by Twoje nazwisko było widoczne publicznie." + +#: forms.py:14 +msgid "E-mail" +msgstr "E-mail" + +#: forms.py:15 +msgid "Won't be publicised." +msgstr "Nie zostanie opublikowany." + +#: forms.py:16 +msgid "Amount" +msgstr "Kwota" + +#: models.py:13 +msgid "author" +msgstr "autor" + +#: models.py:14 +msgid "title" +msgstr "tytuł" + +#: models.py:15 +msgid "slug" +msgstr "slug" + +#: models.py:17 +msgid "Published book." +msgstr "Opublikowana książka." + +#: models.py:18 +msgid "redakcja URL" +msgstr "URL na Redakcji" + +#: models.py:19 +msgid "target" +msgstr "kwota docelowa" + +#: models.py:20 +msgid "start" +msgstr "początek" + +#: models.py:21 +msgid "end" +msgstr "koniec" + +#: models.py:22 +msgid "due" +msgstr "data publikacji" + +#: models.py:23 +msgid "When will it be published if the money is raised." +msgstr "Kiedy książka zostanie opublikowana, jeśli uda się zebrać pieniądze." + +#: models.py:26 +#: models.py:82 +#: models.py:97 +msgid "offer" +msgstr "zbiórka" + +#: models.py:27 +msgid "offers" +msgstr "zbiórki" + +#: models.py:83 +msgid "price" +msgstr "cena" + +#: models.py:84 +#: models.py:98 +msgid "name" +msgstr "nazwa" + +#: models.py:85 +msgid "description" +msgstr "opis" + +#: models.py:88 +msgid "perk" +msgstr "prezent" + +#: models.py:89 +#: models.py:102 +msgid "perks" +msgstr "prezenty" + +#: models.py:99 +msgid "email" +msgstr "e-mail" + +#: models.py:100 +#: models.py:115 +msgid "amount" +msgstr "kwota" + +#: models.py:101 +msgid "payed at" +msgstr "data wpłaty" + +#: models.py:103 +msgid "anonymous" +msgstr "anonimowo" + +#: models.py:106 +msgid "funding" +msgstr "wpłata" + +#: models.py:107 +msgid "fundings" +msgstr "wpłaty" + +#: models.py:116 +msgid "when" +msgstr "kiedy" + +#: models.py:120 +msgid "money spent on a book" +msgstr "pieniądze wydane na książkę" + +#: models.py:121 +msgid "money spent on books" +msgstr "pieniądze wydane na książki" + +#: templates/funding/offer_detail.html:22 +msgid "Support" +msgstr "Wesprzyj" + +#: templates/funding/offer_detail.html:39 +msgid "Supporters" +msgstr "Wpłaty" + +#: templates/funding/offer_detail.html:49 +msgid "Anonymous" +msgstr "Anonim" + +#: templates/funding/offer_list.html:6 +#: templates/funding/offer_list.html:9 +msgid "Support Wolne Lektury" +msgstr "Wspieraj Wolne Lektury" + +#: templates/funding/offer_list.html:16 +msgid "funding closed" +msgstr "zbiórka zakończona" + +#: templates/funding/thanks.html:5 +#: templates/funding/thanks.html.py:9 +msgid "Thank you!" +msgstr "Dziękujemy!" + +#: templates/funding/thanks.html:12 +msgid "Thank you for your support!" +msgstr "Dziękujemy za Twoje wsparcie!" + +#: templates/funding/thanks.html:14 +msgid "Go back to the current fundraiser." +msgstr "Wróć do aktualnej zbiórki." + +#: templates/funding/wlfund.html:4 +#: templates/funding/wlfund.html.py:8 +msgid "Wolne Lektury Fund" +msgstr "Fundusz Wolnych Lektur" + +#: templates/funding/wlfund.html:14 +msgid "Balance" +msgstr "Bilans" + +#: templates/funding/wlfund.html:26 +msgid "Book" +msgstr "Książka" + +#: templates/funding/wlfund.html:34 +msgid "Money from partial fundraiser" +msgstr "Pieniądze z częściowej zbiórki" + +#: templates/funding/tags/funding.html:10 +msgid "Support a book:" +msgstr "Wesprzyj książkę:" + +#: templates/funding/tags/funding.html:14 +msgid "collected" +msgstr "zebrane" + +#: templates/funding/tags/funding.html:16 +msgid "until fundraiser end" +msgstr "do końca zbiórki" + +#: templates/funding/tags/offer_detail_head.html:5 +#, python-format +msgid "" +"If the target is met\n" +" by %(end)s, this book will be published by %(due)s." +msgstr "Jeśli do %(end)s uda się zebrać pełną kwotę, książka zostanie opublikowana do %(due)s." + +#: templates/funding/tags/offer_detail_head.html:10 +#, python-format +msgid "" +"Any excessive money will be transfered to the\n" +" Wolne Lektury Fund." +msgstr "" +"Wszelkie pozostałe środki zostaną przekazane na\n" +" Fundusz Wolnych Lektur." + +#: templates/funding/tags/offer_detail_head.html:15 +msgid "See the published book:" +msgstr "Zobacz opublikowaną książkę:" + +#: templates/funding/tags/offer_detail_head.html:18 +msgid "Funding target has been met!" +msgstr "Udało się zebrać pełną kwotę!" + +#: templates/funding/tags/offer_detail_head.html:19 +msgid "The book will be published by:" +msgstr "Książka zostanie opublikowana do" + +#: templates/funding/tags/offer_detail_head.html:22 +#, python-format +msgid "You can follow the work on the Editorial Platform." +msgstr "Możesz śledzić prace na Platformie Redakcyjnej." + +#: templates/funding/tags/offer_detail_head.html:28 +msgid "Funding target has not been met." +msgstr "Nie udało się zebrać pełnej kwoty." + +#: templates/funding/tags/offer_detail_head.html:31 +#, python-format +msgid "" +"Collected funds have been transfered to the\n" +" Wolne Lektury Fund." +msgstr "" +"Zebrane środki zostały przekazane na\n" +" Fundusz Wolnych Lektur." + +#: templates/funding/widgets/amount.html:12 +msgid "Other amount" +msgstr "Inna kwota" + diff --git a/apps/funding/migrations/0003_auto__add_field_offer_due.py b/apps/funding/migrations/0003_auto__add_field_offer_due.py new file mode 100644 index 000000000..6f9d352ab --- /dev/null +++ b/apps/funding/migrations/0003_auto__add_field_offer_due.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Offer.due' + db.add_column('funding_offer', 'due', + self.gf('django.db.models.fields.DateField')(default=datetime.datetime(2013, 3, 27, 0, 0)), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Offer.due' + db.delete_column('funding_offer', 'due') + + + models = { + 'catalogue.book': { + 'Meta': {'ordering': "('sort_key',)", 'object_name': 'Book'}, + '_related_info': ('jsonfield.fields.JSONField', [], {'null': 'True', 'blank': 'True'}), + 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'common_slug': ('django.db.models.fields.SlugField', [], {'max_length': '120'}), + 'cover': ('catalogue.fields.EbookField', [], {'max_length': '100', 'null': 'True', 'format_name': "'cover'", 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'epub_file': ('catalogue.fields.EbookField', [], {'default': "''", 'max_length': '100', 'format_name': "'epub'", 'blank': 'True'}), + 'extra_info': ('jsonfield.fields.JSONField', [], {'default': "'{}'"}), + 'fb2_file': ('catalogue.fields.EbookField', [], {'default': "''", 'max_length': '100', 'format_name': "'fb2'", 'blank': 'True'}), + 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'html_file': ('catalogue.fields.EbookField', [], {'default': "''", 'max_length': '100', 'format_name': "'html'", 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}), + 'mobi_file': ('catalogue.fields.EbookField', [], {'default': "''", 'max_length': '100', 'format_name': "'mobi'", 'blank': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'pdf_file': ('catalogue.fields.EbookField', [], {'default': "''", 'max_length': '100', 'format_name': "'pdf'", 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '120'}), + 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}), + 'txt_file': ('catalogue.fields.EbookField', [], {'default': "''", 'max_length': '100', 'format_name': "'txt'", 'blank': 'True'}), + 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'xml_file': ('catalogue.fields.EbookField', [], {'default': "''", 'max_length': '100', 'format_name': "'xml'", 'blank': 'True'}) + }, + 'funding.funding': { + 'Meta': {'ordering': "['-payed_at']", 'object_name': 'Funding'}, + 'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '2'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '127'}), + 'offer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['funding.Offer']"}), + 'payed_at': ('django.db.models.fields.DateTimeField', [], {}), + 'perks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['funding.Perk']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'funding.offer': { + 'Meta': {'ordering': "['-end']", 'object_name': 'Offer'}, + 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']", 'null': 'True', 'blank': 'True'}), + 'due': ('django.db.models.fields.DateField', [], {}), + 'end': ('django.db.models.fields.DateField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'redakcja_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), + 'start': ('django.db.models.fields.DateField', [], {}), + 'target': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '2'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'funding.perk': { + 'Meta': {'ordering': "['-price']", 'object_name': 'Perk'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'offer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['funding.Offer']", 'null': 'True', 'blank': 'True'}), + 'price': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '2'}) + }, + 'funding.spent': { + 'Meta': {'object_name': 'Spent'}, + 'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '2'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'timestamp': ('django.db.models.fields.DateField', [], {}) + } + } + + complete_apps = ['funding'] \ No newline at end of file diff --git a/apps/funding/migrations/0004_auto__add_field_funding_anonymous.py b/apps/funding/migrations/0004_auto__add_field_funding_anonymous.py new file mode 100644 index 000000000..d8c4fafbc --- /dev/null +++ b/apps/funding/migrations/0004_auto__add_field_funding_anonymous.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Funding.anonymous' + db.add_column('funding_funding', 'anonymous', + self.gf('django.db.models.fields.BooleanField')(default=False), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Funding.anonymous' + db.delete_column('funding_funding', 'anonymous') + + + models = { + 'catalogue.book': { + 'Meta': {'ordering': "('sort_key',)", 'object_name': 'Book'}, + '_related_info': ('jsonfield.fields.JSONField', [], {'null': 'True', 'blank': 'True'}), + 'changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), + 'common_slug': ('django.db.models.fields.SlugField', [], {'max_length': '120'}), + 'cover': ('catalogue.fields.EbookField', [], {'max_length': '100', 'null': 'True', 'format_name': "'cover'", 'blank': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'epub_file': ('catalogue.fields.EbookField', [], {'default': "''", 'max_length': '100', 'format_name': "'epub'", 'blank': 'True'}), + 'extra_info': ('jsonfield.fields.JSONField', [], {'default': "'{}'"}), + 'fb2_file': ('catalogue.fields.EbookField', [], {'default': "''", 'max_length': '100', 'format_name': "'fb2'", 'blank': 'True'}), + 'gazeta_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'html_file': ('catalogue.fields.EbookField', [], {'default': "''", 'max_length': '100', 'format_name': "'html'", 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'default': "'pol'", 'max_length': '3', 'db_index': 'True'}), + 'mobi_file': ('catalogue.fields.EbookField', [], {'default': "''", 'max_length': '100', 'format_name': "'mobi'", 'blank': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Book']"}), + 'parent_number': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'pdf_file': ('catalogue.fields.EbookField', [], {'default': "''", 'max_length': '100', 'format_name': "'pdf'", 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '120'}), + 'sort_key': ('django.db.models.fields.CharField', [], {'max_length': '120', 'db_index': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '120'}), + 'txt_file': ('catalogue.fields.EbookField', [], {'default': "''", 'max_length': '100', 'format_name': "'txt'", 'blank': 'True'}), + 'wiki_link': ('django.db.models.fields.CharField', [], {'max_length': '240', 'blank': 'True'}), + 'xml_file': ('catalogue.fields.EbookField', [], {'default': "''", 'max_length': '100', 'format_name': "'xml'", 'blank': 'True'}) + }, + 'funding.funding': { + 'Meta': {'ordering': "['-payed_at']", 'object_name': 'Funding'}, + 'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '2'}), + 'anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '127'}), + 'offer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['funding.Offer']"}), + 'payed_at': ('django.db.models.fields.DateTimeField', [], {}), + 'perks': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['funding.Perk']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'funding.offer': { + 'Meta': {'ordering': "['-end']", 'object_name': 'Offer'}, + 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']", 'null': 'True', 'blank': 'True'}), + 'due': ('django.db.models.fields.DateField', [], {}), + 'end': ('django.db.models.fields.DateField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'redakcja_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), + 'start': ('django.db.models.fields.DateField', [], {}), + 'target': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '2'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'funding.perk': { + 'Meta': {'ordering': "['-price']", 'object_name': 'Perk'}, + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'offer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['funding.Offer']", 'null': 'True', 'blank': 'True'}), + 'price': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '2'}) + }, + 'funding.spent': { + 'Meta': {'object_name': 'Spent'}, + 'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '2'}), + 'book': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Book']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'timestamp': ('django.db.models.fields.DateField', [], {}) + } + } + + complete_apps = ['funding'] \ No newline at end of file diff --git a/apps/funding/models.py b/apps/funding/models.py index 8c1ee9a72..579147cb4 100644 --- a/apps/funding/models.py +++ b/apps/funding/models.py @@ -13,11 +13,14 @@ class Offer(models.Model): author = models.CharField(_('author'), max_length=255) title = models.CharField(_('title'), max_length=255) slug = models.SlugField(_('slug')) - book = models.ForeignKey(Book, null=True, blank=True) + book = models.ForeignKey(Book, null=True, blank=True, + help_text=_('Published book.')) redakcja_url = models.URLField(_('redakcja URL'), blank=True) target = models.DecimalField(_('target'), decimal_places=2, max_digits=10) start = models.DateField(_('start')) end = models.DateField(_('end')) + due = models.DateField(_('due'), + help_text=_('When will it be published if the money is raised.')) class Meta: verbose_name = _('offer') @@ -30,6 +33,9 @@ class Offer(models.Model): def get_absolute_url(self): return reverse('funding_offer', args=[self.slug]) + def is_current(self): + return self.start <= date.today() <= self.end + @classmethod def current(cls): today = date.today() @@ -52,9 +58,10 @@ class Offer(models.Model): perks = perks.filter(price__lte=amount) return perks - def fund(self, name, email, amount): + def fund(self, name, email, amount, anonymous=False): funding = self.funding_set.create( name=name, email=email, amount=amount, + anonymous=anonymous, payed_at=datetime.now()) funding.perks = self.get_perks(amount) return funding @@ -91,8 +98,9 @@ class Funding(models.Model): name = models.CharField(_('name'), max_length=127) email = models.EmailField(_('email')) amount = models.DecimalField(_('amount'), decimal_places=2, max_digits=10) - payed_at = models.DateTimeField(_('payed_at')) + payed_at = models.DateTimeField(_('payed at')) perks = models.ManyToManyField(Perk, verbose_name=_('perks'), blank=True) + anonymous = models.BooleanField(_('anonymous')) class Meta: verbose_name = _('funding') @@ -108,5 +116,10 @@ class Spent(models.Model): timestamp = models.DateField(_('when')) book = models.ForeignKey(Book) + class Meta: + verbose_name = _('money spent on a book') + verbose_name_plural = _('money spent on books') + ordering = ['-timestamp'] + def __unicode__(self): return u"Spent: %s" % unicode(self.book) diff --git a/apps/funding/static/funding/funding.css b/apps/funding/static/funding/funding.css index 0b3f56344..6c3ef37b4 100755 --- a/apps/funding/static/funding/funding.css +++ b/apps/funding/static/funding/funding.css @@ -1,27 +1 @@ -.funding { - font-size: 1.5em; - padding: 0; - padding: .5em 1em; - background-image: url(/static/img/green-pixel.png); - background-repeat: repeat-y; - background-color: rgba(13, 126, 133, 0.2); } - .funding a { - display: block; - color: black; } - -.wlfund { - width: 100%; - border-collapse: collapse; } - .wlfund td { - padding: 0 0 1em 0; - text-align: center; } - .wlfund td:last-child { - text-align: right; } - .wlfund td div { - padding: 1em; - box-shadow: 0 2px 0 #DDDDDD; } - .wlfund .funding-plus td div { - background: rgba(13, 126, 133, 0.2); } - .wlfund .funding-minus td div { - bbackground: rgba(255, 0, 0, 0.2); - background: white; } +a.funding{font-size:1.5em;padding:.5em 1em;display:block;color:#000;background-image:url(/static/img/green-pixel.png);background-repeat:repeat-y;background-color:rgba(13, 126, 133, .2)}.wlfund{width:100%;border-collapse:collapse}.wlfund td{padding:0 0 1em 0;text-align:center}.wlfund td:last-child{text-align:right}.wlfund td div{padding:1em;box-shadow:0 2px 0 #DDD}.wlfund .funding-plus td div{background:rgba(13, 126, 133, .2)}.wlfund .funding-minus td div{bbackground:rgba(255, 0, 0, .2);background:#fff}.funding-current{margin-bottom:3em}.funding-stale .funding{opacity:.5} \ No newline at end of file diff --git a/apps/funding/static/funding/funding.scss b/apps/funding/static/funding/funding.scss index 8de3ef8a7..c5e8c48a3 100755 --- a/apps/funding/static/funding/funding.scss +++ b/apps/funding/static/funding/funding.scss @@ -1,15 +1,12 @@ -.funding { +a.funding { font-size: 1.5em; - padding: 0; padding: .5em 1em; + display: block; + color: black; + background-image: url(/static/img/green-pixel.png); background-repeat: repeat-y; background-color: fade-out(#0D7E85, .8); - - a { - display: block; - color: black; - } } .wlfund { @@ -39,3 +36,13 @@ background: white; } } + + +.funding-current { + margin-bottom: 3em; +} +.funding-stale { + .funding { + opacity: .5; + } +} diff --git a/apps/funding/templates/funding/offer_detail.html b/apps/funding/templates/funding/offer_detail.html index 3f49b8828..53c514f79 100755 --- a/apps/funding/templates/funding/offer_detail.html +++ b/apps/funding/templates/funding/offer_detail.html @@ -1,50 +1,65 @@ {% extends "base.html" %} {% load url from future %} +{% load i18n %} {% load funding_tags %} -{% load uni_form_tags %} + {% block titleextra %}{{ object }}{% endblock %} + {% block body %}

{{ object }}

-
+
-{% funding object %} + {% funding object %} + {% offer_detail_head object %} -{% if object.state == 'running' %} + {% if object.state == 'running' %} +
+ +

{% trans "Support" %}

+
+ {% csrf_token %} + + {{ form.as_table }} + +
+
+ +
+ {% endif %} + +

Zobacz wszystkie zbiórki.

-

Wpłać:

-
- {% csrf_token %} - {{ form|as_uni_form }} - -
-
-{% endif %} - -

Zobacz wszystkie zbiórki.

-

Wpłaty:

+

{% trans "Supporters" %}:

+ + {% for funding in object.funding_set.all %} + + + + + + {% endfor %} +
{{ funding.payed_at.date }}
+ {% if funding.anonymous %} + {% trans "Anonymous" %} + {% else %} + {{ funding.name }} + {% endif %} +
+{{ funding.amount }} zł
+ {% for perk in funding.perks.all %} + {{ perk.name }}{% if not forloop.last %},{% endif %} + {% endfor %}  +
-
    -{% for funding in object.funding_set.all %} -
  • {{ funding.name }}: {{ funding.amount }} zł
  • -{% endfor %} -
{% endblock %} diff --git a/apps/funding/templates/funding/offer_list.html b/apps/funding/templates/funding/offer_list.html index 6114e377c..44cd82815 100755 --- a/apps/funding/templates/funding/offer_list.html +++ b/apps/funding/templates/funding/offer_list.html @@ -1,27 +1,23 @@ {% extends "base.html" %} {% load url from future %} +{% load i18n %} {% load funding_tags %} -{% block titleextra %}Funding{% endblock %} +{% block titleextra %}{% trans "Support Wolne Lektury" %}{% endblock %} {% block body %} -

Funding!

+

{% trans "Support Wolne Lektury" %}

    {% for funding in object_list %} -
  • -

    {{ funding.start }} – {{ funding.end }}

    - {% funding funding link=1 add_class="standalone-funding" %} - {% if funding.state == "win" %} - {% if funding.book_url %} -

    Zobacz opublikowaną książkę

    - {% else %} -

    Termin publikacji: {{ funding.term }}

    - {% endif %} - {% elif funding.state == "lose" %} -

    Cel finansowania nie został osiągnięty. Zgromadzone środki - zostały przekazane na Fundusz Wolnych Lektur.

    +
  • +

    {{ funding.start }} – {{ funding.end }} + {% if not funding.is_current %} + ({% trans "funding closed" %}) {% endif %} +

    + {% funding funding link=1 %} + {% offer_detail_head funding %}
  • {% endfor %}
diff --git a/apps/funding/templates/funding/tags/funding.html b/apps/funding/templates/funding/tags/funding.html index 7d72c3161..981ae62f5 100755 --- a/apps/funding/templates/funding/tags/funding.html +++ b/apps/funding/templates/funding/tags/funding.html @@ -1,11 +1,23 @@ +{% load i18n %} {% load time_tags %} - + {{ offer }} + + {% trans "collected" %}: {{ offer.sum }} / {{ offer.target }} zł + {% if is_current %} + {% trans "until fundraiser end" %}: + + + {% endif %} + +
+ +{% endif %} diff --git a/apps/funding/templates/funding/tags/offer_detail_head.html b/apps/funding/templates/funding/tags/offer_detail_head.html new file mode 100644 index 000000000..000b9b8a3 --- /dev/null +++ b/apps/funding/templates/funding/tags/offer_detail_head.html @@ -0,0 +1,35 @@ +{% load url from future %} +{% load i18n %} +{% if state == "running" %} +

+ {% blocktrans with due=offer.due end=offer.end %}If the target is met + by {{ end }}, this book will be published by {{ due }}.{% endblocktrans %} +

+

+ {% url 'funding_wlfund' as url %} + {% blocktrans %}Any excessive money will be transfered to the + Wolne Lektury Fund.{% endblocktrans %} +

+{% elif state == "win" %} + {% if offer.book %} +

{% trans "See the published book:" %} + {{ offer.book }}.

+ {% else %} +

{% trans "Funding target has been met!" %}

+

{% trans "The book will be published by:" %} {{ offer.due }}. + {% if offer.redakcja_url %} + {% with url=offer.redakcja_url %} + {% blocktrans %}You can follow the work on the Editorial Platform.{% endblocktrans %} + {% endwith %} + {% endif %} +

+ {% endif %} +{% elif state == "lose" %} +

{% trans "Funding target has not been met." %} + {% if offer.sum %} + {% url 'funding_wlfund' as url %} + {% blocktrans %}Collected funds have been transfered to the + Wolne Lektury Fund.{% endblocktrans %} + {% endif %} +

+{% endif %} diff --git a/apps/funding/templates/funding/thanks.html b/apps/funding/templates/funding/thanks.html index baf227558..2863c6917 100755 --- a/apps/funding/templates/funding/thanks.html +++ b/apps/funding/templates/funding/thanks.html @@ -1,16 +1,17 @@ {% extends "base.html" %} +{% load i18n %} {% load funding_tags %} -{% block titleextra %}Dziękujemy!{% endblock %} +{% block titleextra %}{% trans "Thank you!" %}{% endblock %} {% block body %} -

Dziękujemy!

+

{% trans "Thank you!" %}

-Dziękujemy za wsparcie! +{% trans "Thank you for your support!" %} -

Wróć do strony aktualnej zbiórki.

+

{% trans "Go back to the current fundraiser." %}

diff --git a/apps/funding/templates/funding/widgets/amount.html b/apps/funding/templates/funding/widgets/amount.html new file mode 100644 index 000000000..edb7a51a1 --- /dev/null +++ b/apps/funding/templates/funding/widgets/amount.html @@ -0,0 +1,17 @@ +{% load i18n %} +{% if perks %} + {% for perk in perks %} +
+ {% endfor %} + + zł +{% else %} + +{% endif %} diff --git a/apps/funding/templates/funding/wlfund.html b/apps/funding/templates/funding/wlfund.html index 5f3ca65b5..8188f1a86 100755 --- a/apps/funding/templates/funding/wlfund.html +++ b/apps/funding/templates/funding/wlfund.html @@ -1,33 +1,37 @@ {% extends "base.html" %} +{% load i18n %} -{% block titleextra %}Fundusz Wolnych Lektur{% endblock %} +{% block titleextra %}{% trans "Wolne Lektury Fund" %}{% endblock %} {% block body %} -

Fundusz Wolnych Lektur

+

{% trans "Wolne Lektury Fund" %}

- - - + + + + + + {% for tag, entry in log %} {% if tag == 'spent' %} - - - + + {% else %} - - - + + diff --git a/apps/funding/templatetags/funding_tags.py b/apps/funding/templatetags/funding_tags.py index c533672b5..5331f8c4a 100755 --- a/apps/funding/templatetags/funding_tags.py +++ b/apps/funding/templatetags/funding_tags.py @@ -8,10 +8,22 @@ register = template.Library() def funding(offer=None, link=False, add_class=""): if offer is None: offer = Offer.current() + if offer is None: + return {} return { 'offer': offer, + 'is_current': offer.is_current(), 'percentage': 100 * offer.sum() / offer.target, 'link': link, 'add_class': add_class, } + + +@register.inclusion_tag("funding/tags/offer_detail_head.html") +def offer_detail_head(offer): + return { + 'offer': offer, + 'state': offer.state(), + } + diff --git a/apps/funding/views.py b/apps/funding/views.py index 05bf3983c..f54316208 100644 --- a/apps/funding/views.py +++ b/apps/funding/views.py @@ -1,4 +1,8 @@ -# Create your views here. +# -*- coding: utf-8 -*- +# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from django.conf import settings from django.core.urlresolvers import reverse from django.shortcuts import redirect, get_object_or_404 from django.views.generic import TemplateView, FormView, DetailView @@ -65,7 +69,7 @@ class OfferDetailView(FormView): if self.request.method == 'POST': return form_class(self.object, self.request.POST) else: - return form_class(self.object) + return form_class(self.object, initial={'amount': settings.FUNDING_DEFAULT}) def get_context_data(self, *args, **kwargs): ctx = super(OfferDetailView, self).get_context_data(*args, **kwargs) diff --git a/apps/funding/widgets.py b/apps/funding/widgets.py new file mode 100644 index 000000000..cd7bf9b0d --- /dev/null +++ b/apps/funding/widgets.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# +from decimal import Decimal +from django.conf import settings +from django import forms +from django.template.loader import render_to_string + + +class PerksAmountWidget(forms.Textarea): + def perks_input_name(self, name): + return "_%s_perk" % name + + def render(self, name, value, attrs=None): + try: + value = Decimal(value) + except: + pass + perks = list(self.form_instance.offer.get_perks()) + perk_chosen = False + for perk in perks: + if perk.price == value: + perk.chosen = True + perk_chosen = True + break + + return render_to_string("funding/widgets/amount.html", { + "perks": perks, + "name": name, + "perks_input_name": self.perks_input_name(name), + "perk_chosen": perk_chosen, + "value": value, + "attrs": attrs, + }) + + def value_from_datadict(self, data, files, name): + num_str = data.get(self.perks_input_name(name)) or data[name] + return num_str.replace(',', '.') + + diff --git a/apps/wolnelektury_core/static/css/form.css b/apps/wolnelektury_core/static/css/form.css new file mode 100644 index 000000000..65d0482ef --- /dev/null +++ b/apps/wolnelektury_core/static/css/form.css @@ -0,0 +1 @@ +form table th{vertical-align:top;text-align:left;font-weight:normal}form table td{padding-bottom:1em}form table .required th:after{content:" *"}form table .errorlist{color:red;margin:0;padding:0;list-style:none}form table .helptext{color:#888;font-size:.9em;font-style:italic} \ No newline at end of file diff --git a/apps/wolnelektury_core/static/css/form.scss b/apps/wolnelektury_core/static/css/form.scss new file mode 100644 index 000000000..42e867595 --- /dev/null +++ b/apps/wolnelektury_core/static/css/form.scss @@ -0,0 +1,26 @@ +form table { + th { + vertical-align: top; + text-align: left; + font-weight: normal; + } + td { + padding-bottom: 1em; + } + + .required th:after { + content: " *"; + } + + .errorlist { + color: red; + margin: 0; + padding: 0; + list-style: none; + } + .helptext { + color: #888; + font-size: .9em; + font-style: italic; + } +} diff --git a/lib/pyscss_compiler.py b/lib/pyscss_compiler.py new file mode 100644 index 000000000..b57d59e0b --- /dev/null +++ b/lib/pyscss_compiler.py @@ -0,0 +1,19 @@ +from os.path import dirname +from django.conf import settings +from pipeline.compilers import SubProcessCompiler + + +class PySCSSCompiler(SubProcessCompiler): + output_extension = 'css' + + def match_file(self, filename): + return filename.endswith('.scss') + + def compile_file(self, infile, outfile, outdated=False, force=False): + command = "%s %s < %s > %s" % ( + settings.PIPELINE_PYSCSS_BINARY, + settings.PIPELINE_PYSCSS_ARGUMENTS, + infile, + outfile + ) + return self.execute_command(command, cwd=dirname(infile)) diff --git a/requirements.txt b/requirements.txt index 645015cb5..300b22ee9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ South>=0.7 # migrations for django django-pipeline>=1.2.24,<1.3 django-pagination>=1.0 django-maintenancemode>=0.10 -django-piston==0.2.2 +django-piston>=0.2.2,<0.2.3 #django-jsonfield -e git+git://github.com/bradjasper/django-jsonfield.git@2f427368ad70bf8d9a0580df58ec0eb0654d62ae#egg=django-jsonfield django-picklefield @@ -15,6 +15,7 @@ django-picklefield # version of django-allauth 0.4 with install script fixed -e git+git://github.com/pennersr/django-allauth.git@3a03db9b2ecca370af228df367bd8fa52afea5ea#egg=django-allauth pytz +pyScss django-honeypot django-uni-form diff --git a/wolnelektury/settings/custom.py b/wolnelektury/settings/custom.py index a0bab7a64..8e2489d79 100644 --- a/wolnelektury/settings/custom.py +++ b/wolnelektury/settings/custom.py @@ -16,3 +16,5 @@ CATALOGUE_CUSTOMPDF_RATE_LIMIT = '1/m' # set to 'new' or 'old' to skip time-consuming test # for TeX morefloats library version LIBRARIAN_PDF_MOREFLOATS = None + +FUNDING_DEFAULT = 20 diff --git a/wolnelektury/settings/static.py b/wolnelektury/settings/static.py index 8d0e5bec2..83de0da2c 100644 --- a/wolnelektury/settings/static.py +++ b/wolnelektury/settings/static.py @@ -31,6 +31,7 @@ PIPELINE_CSS = { 'sponsors/css/sponsors.css', 'css/auth.css', 'funding/funding.scss', + 'css/form.scss', 'css/social/shelf_tags.css', 'css/ui-lightness/jquery-ui-1.8.16.custom.css', @@ -123,5 +124,7 @@ PIPELINE_CSS_COMPRESSOR = None PIPELINE_JS_COMPRESSOR = None PIPELINE_COMPILERS = ( - 'pipeline.compilers.sass.SASSCompiler', + 'pyscss_compiler.PySCSSCompiler', ) +PIPELINE_PYSCSS_BINARY = '/usr/bin/env pyscss' +PIPELINE_PYSCSS_ARGUMENTS = '' -- 2.20.1
Bilans:
{{ amount }}
 
{% trans "Balance" %}:
 
{{ amount }} zł
 
{{ entry.timestamp }}
{{ entry.total }}
-{{ entry.amount }} zł
Książka: +
-{{ entry.amount }} zł
{{ entry.total }} zł
{% trans "Book" %}: {{ entry.book }}
{{ entry.end }}
{{ entry.total }}
+{{ entry.sum }} zł
Pieniądze z częściowej zbiórki: +
+{{ entry.sum }} zł
{{ entry.total }} zł
{% trans "Money from partial fundraiser" %}: {{ entry }}