From: Radek Czajka Date: Wed, 10 Apr 2013 08:00:34 +0000 (+0200) Subject: Merge branch 'master' into funding X-Git-Url: https://git.mdrn.pl/wolnelektury.git/commitdiff_plain/6d7b11e4a01b2314c0527d94d91f3159c0bd34ce?hp=02fe6525fe7fa7c16d6e1dfe0ca040ac07d41325 Merge branch 'master' into funding Conflicts: requirements.txt --- diff --git a/.gitignore b/.gitignore index c3e3e48b6..6714e2301 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ dev.sqlite *~ *.orig *.log +.sass-cache # Compress output /static diff --git a/apps/funding/__init__.py b/apps/funding/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/funding/admin.py b/apps/funding/admin.py new file mode 100755 index 000000000..7fbb4c488 --- /dev/null +++ b/apps/funding/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from .models import Offer, Perk, Funding, Spent + +admin.site.register(Spent) +admin.site.register(Offer) +admin.site.register(Perk) +admin.site.register(Funding) diff --git a/apps/funding/forms.py b/apps/funding/forms.py new file mode 100755 index 000000000..6b2316e80 --- /dev/null +++ b/apps/funding/forms.py @@ -0,0 +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): + 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(__("Enter positive amount.")) + return self.cleaned_data['amount'] + + def clean(self): + if not self.offer.is_current(): + raise forms.ValidationError(__("This offer is out of date.")) + return self.cleaned_data + + def save(self): + return self.offer.fund( + name=self.cleaned_data['name'], + email=self.cleaned_data['email'], + 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 000000000..3c69a40bb Binary files /dev/null and b/apps/funding/locale/pl/LC_MESSAGES/django.mo differ 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..c3f843cb5 --- /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:36+0100\n" +"PO-Revision-Date: 2013-03-27 17:36+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 "Excessive money from fundraiser" +msgstr "Pieniądze pozostałe ze 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/0001_initial.py b/apps/funding/migrations/0001_initial.py new file mode 100644 index 000000000..924ef7728 --- /dev/null +++ b/apps/funding/migrations/0001_initial.py @@ -0,0 +1,102 @@ +# -*- 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 model 'Offer' + db.create_table('funding_offer', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('author', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('title', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('slug', self.gf('django.db.models.fields.SlugField')(max_length=50)), + ('book_url', self.gf('django.db.models.fields.URLField')(max_length=200, blank=True)), + ('redakcja_url', self.gf('django.db.models.fields.URLField')(max_length=200, blank=True)), + ('target', self.gf('django.db.models.fields.DecimalField')(max_digits=10, decimal_places=2)), + ('start', self.gf('django.db.models.fields.DateField')()), + ('end', self.gf('django.db.models.fields.DateField')()), + )) + db.send_create_signal('funding', ['Offer']) + + # Adding model 'Perk' + db.create_table('funding_perk', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('offer', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['funding.Offer'], null=True)), + ('price', self.gf('django.db.models.fields.DecimalField')(max_digits=10, decimal_places=2)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('description', self.gf('django.db.models.fields.TextField')(blank=True)), + )) + db.send_create_signal('funding', ['Perk']) + + # Adding model 'Funding' + db.create_table('funding_funding', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('offer', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['funding.Offer'])), + ('name', self.gf('django.db.models.fields.CharField')(max_length=127)), + ('email', self.gf('django.db.models.fields.EmailField')(max_length=75)), + ('amount', self.gf('django.db.models.fields.DecimalField')(max_digits=10, decimal_places=2)), + ('payed_at', self.gf('django.db.models.fields.DateTimeField')()), + )) + db.send_create_signal('funding', ['Funding']) + + # Adding M2M table for field perks on 'Funding' + db.create_table('funding_funding_perks', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('funding', models.ForeignKey(orm['funding.funding'], null=False)), + ('perk', models.ForeignKey(orm['funding.perk'], null=False)) + )) + db.create_unique('funding_funding_perks', ['funding_id', 'perk_id']) + + + def backwards(self, orm): + # Deleting model 'Offer' + db.delete_table('funding_offer') + + # Deleting model 'Perk' + db.delete_table('funding_perk') + + # Deleting model 'Funding' + db.delete_table('funding_funding') + + # Removing M2M table for field perks on 'Funding' + db.delete_table('funding_funding_perks') + + + models = { + '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'}) + }, + 'funding.offer': { + 'Meta': {'ordering': "['-end']", 'object_name': 'Offer'}, + 'author': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'book_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + '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'}), + 'price': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '2'}) + } + } + + complete_apps = ['funding'] \ No newline at end of file diff --git a/apps/funding/migrations/0002_auto__add_spent__del_field_offer_book_url__add_field_offer_book.py b/apps/funding/migrations/0002_auto__add_spent__del_field_offer_book_url__add_field_offer_book.py new file mode 100644 index 000000000..1c03bdbf3 --- /dev/null +++ b/apps/funding/migrations/0002_auto__add_spent__del_field_offer_book_url__add_field_offer_book.py @@ -0,0 +1,108 @@ +# -*- 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 model 'Spent' + db.create_table('funding_spent', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('amount', self.gf('django.db.models.fields.DecimalField')(max_digits=10, decimal_places=2)), + ('timestamp', self.gf('django.db.models.fields.DateField')()), + ('book', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Book'])), + )) + db.send_create_signal('funding', ['Spent']) + + # Deleting field 'Offer.book_url' + db.delete_column('funding_offer', 'book_url') + + # Adding field 'Offer.book' + db.add_column('funding_offer', 'book', + self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Book'], null=True, blank=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting model 'Spent' + db.delete_table('funding_spent') + + # Adding field 'Offer.book_url' + db.add_column('funding_offer', 'book_url', + self.gf('django.db.models.fields.URLField')(default='', max_length=200, blank=True), + keep_default=False) + + # Deleting field 'Offer.book' + db.delete_column('funding_offer', 'book_id') + + + 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'}), + '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/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/migrations/__init__.py b/apps/funding/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/funding/models.py b/apps/funding/models.py new file mode 100644 index 000000000..579147cb4 --- /dev/null +++ b/apps/funding/models.py @@ -0,0 +1,125 @@ +# -*- 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.core.urlresolvers import reverse +from django.db import models +from django.utils.translation import ugettext_lazy as _, ugettext as __ +from datetime import date, datetime +from catalogue.models import Book + + +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, + 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') + verbose_name_plural = _('offers') + ordering = ['-end'] + + def __unicode__(self): + return u"%s – %s" % (self.author, self.title) + + 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() + objects = cls.objects.filter(start__lte=today, end__gte=today) + try: + return objects[0] + except IndexError: + return None + + @classmethod + def public(cls): + today = date.today() + return cls.objects.filter(start__lte=today) + + def get_perks(self, amount=None): + perks = Perk.objects.filter( + models.Q(offer=self) | models.Q(offer=None) + ) + if amount is not None: + perks = perks.filter(price__lte=amount) + return perks + + 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 + + def sum(self): + return self.funding_set.aggregate(s=models.Sum('amount'))['s'] or 0 + + def state(self): + if self.sum() >= self.target: + return 'win' + elif self.start <= date.today() <= self.end: + return 'running' + else: + return 'lose' + + +class Perk(models.Model): + offer = models.ForeignKey(Offer, verbose_name=_('offer'), null=True, blank=True) + price = models.DecimalField(_('price'), decimal_places=2, max_digits=10) + name = models.CharField(_('name'), max_length=255) + description = models.TextField(_('description'), blank=True) + + class Meta: + verbose_name = _('perk') + verbose_name_plural = _('perks') + ordering = ['-price'] + + def __unicode__(self): + return "%s (%s%s)" % (self.name, self.price, u" for %s" % self.offer if self.offer else "") + + +class Funding(models.Model): + offer = models.ForeignKey(Offer, verbose_name=_('offer')) + 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')) + perks = models.ManyToManyField(Perk, verbose_name=_('perks'), blank=True) + anonymous = models.BooleanField(_('anonymous')) + + class Meta: + verbose_name = _('funding') + verbose_name_plural = _('fundings') + ordering = ['-payed_at'] + + def __unicode__(self): + return "%s payed %s for %s" % (self.name, self.amount, self.offer) + + +class Spent(models.Model): + amount = models.DecimalField(_('amount'), decimal_places=2, max_digits=10) + 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 new file mode 100755 index 000000000..6c3ef37b4 --- /dev/null +++ b/apps/funding/static/funding/funding.css @@ -0,0 +1 @@ +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 new file mode 100755 index 000000000..c5e8c48a3 --- /dev/null +++ b/apps/funding/static/funding/funding.scss @@ -0,0 +1,48 @@ +a.funding { + font-size: 1.5em; + 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); +} + +.wlfund { + width: 100%; + border-collapse: collapse; + + td { + padding: 0 0 1em 0; + text-align: center; + } + + td:last-child { + text-align: right; + } + + td div { + padding: 1em; + box-shadow: 0 2px 0 #DDDDDD; + } + + .funding-plus td div { + background: fade-out(#0D7E85, .8); + } + + .funding-minus td div { + bbackground: fade-out(red, .8); + 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 new file mode 100755 index 000000000..53c514f79 --- /dev/null +++ b/apps/funding/templates/funding/offer_detail.html @@ -0,0 +1,65 @@ +{% extends "base.html" %} +{% load url from future %} +{% load i18n %} +{% load funding_tags %} + + +{% block titleextra %}{{ object }}{% endblock %} + + +{% block body %} + +

{{ object }}

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

{% trans "Support" %}

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

Zobacz wszystkie zbiórki.

+ +
+ + +

{% 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 %}  +
+ +
+ +{% endblock %} diff --git a/apps/funding/templates/funding/offer_list.html b/apps/funding/templates/funding/offer_list.html new file mode 100755 index 000000000..44cd82815 --- /dev/null +++ b/apps/funding/templates/funding/offer_list.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} +{% load url from future %} +{% load i18n %} +{% load funding_tags %} + +{% block titleextra %}{% trans "Support Wolne Lektury" %}{% endblock %} + +{% block body %} +

{% trans "Support Wolne Lektury" %}

+ +
    +{% for funding in object_list %} +
  • +

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

    + {% funding funding link=1 %} + {% offer_detail_head funding %} +
  • +{% endfor %} +
+ +{% endblock %} diff --git a/apps/funding/templates/funding/tags/funding.html b/apps/funding/templates/funding/tags/funding.html new file mode 100755 index 000000000..981ae62f5 --- /dev/null +++ b/apps/funding/templates/funding/tags/funding.html @@ -0,0 +1,23 @@ +{% load i18n %} +{% load time_tags %} +{% if offer %} + + {% if is_current %} + {% trans "Support a book:" %} + {% endif %} + {{ 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 new file mode 100755 index 000000000..2863c6917 --- /dev/null +++ b/apps/funding/templates/funding/thanks.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} +{% load i18n %} +{% load funding_tags %} + +{% block titleextra %}{% trans "Thank you!" %}{% endblock %} + +{% block body %} + +

{% trans "Thank you!" %}

+
+ +{% trans "Thank you for your support!" %} + +

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

+
+ + +{% endblock %} 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 new file mode 100755 index 000000000..da35943c3 --- /dev/null +++ b/apps/funding/templates/funding/wlfund.html @@ -0,0 +1,43 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block titleextra %}{% trans "Wolne Lektury Fund" %}{% endblock %} + +{% block body %} + +

{% trans "Wolne Lektury Fund" %}

+ + + + + + + + + + + + {% for tag, entry in log %} + {% if tag == 'spent' %} + + + + + + + {% else %} + + + + + + + {% endif %} + {% endfor %} +
{% trans "Balance" %}:
 
{{ amount }} zł
 
{{ entry.timestamp }}
-{{ entry.amount }} zł
{{ entry.total }} zł
{% trans "Book" %}: + {{ entry.book }}
{{ entry.end }}
+{{ entry.wlfund }} zł
{{ entry.total }} zł
{% trans "Excessive money from fundraiser" %}: + + {{ entry }}
+ + +{% endblock %} diff --git a/apps/funding/templatetags/__init__.py b/apps/funding/templatetags/__init__.py new file mode 100755 index 000000000..e69de29bb diff --git a/apps/funding/templatetags/funding_tags.py b/apps/funding/templatetags/funding_tags.py new file mode 100755 index 000000000..5331f8c4a --- /dev/null +++ b/apps/funding/templatetags/funding_tags.py @@ -0,0 +1,29 @@ +from django import template +from ..models import Offer + +register = template.Library() + + +@register.inclusion_tag("funding/tags/funding.html") +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/tests.py b/apps/funding/tests.py new file mode 100644 index 000000000..c9438a878 --- /dev/null +++ b/apps/funding/tests.py @@ -0,0 +1,28 @@ +# -*- 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.test import TestCase +from .models import Offer, Perk, Funding + + +class FundTest(TestCase): + def setUp(self): + self.offer1 = Offer.objects.create( + author='author1', title='title1', slug='slug1', + target=100, start='2013-03-01', end='2013-03-31') + + def test_perks(self): + perk = Perk.objects.create(price=20, name='Perk 20') + perk1 = Perk.objects.create(offer=self.offer1, price=50, name='Perk 50') + offer2 = Offer.objects.create( + author='author2', title='title2', slug='slug2', + target=100, start='2013-02-01', end='2013-02-20') + perk2 = Perk.objects.create(offer=offer2, price=1, name='Perk 1') + + self.assertEqual( + set(self.offer1.fund('Tester', 'test@example.com', 10).perks.all()), + set()) + self.assertEqual( + set(self.offer1.fund('Tester', 'test@example.com', 70).perks.all()), + set([perk, perk1])) diff --git a/apps/funding/urls.py b/apps/funding/urls.py new file mode 100755 index 000000000..dc79f3130 --- /dev/null +++ b/apps/funding/urls.py @@ -0,0 +1,17 @@ +# -*- 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.urls import patterns, url +from django.views.generic import ListView, FormView, TemplateView + +from .models import Offer +from .views import WLFundView, OfferDetailView, ThanksView + + +urlpatterns = patterns('', + url(r'^$', ListView.as_view(queryset=Offer.public()), name='funding'), + url(r'^lektura/(?P[^/]+)/$', OfferDetailView.as_view(), name='funding_offer'), + url(r'^dziekujemy/$', ThanksView.as_view(), name='funding_thanks'), + url(r'^fundusz/$', WLFundView.as_view(), name='funding_wlfund'), +) diff --git a/apps/funding/views.py b/apps/funding/views.py new file mode 100644 index 000000000..c14cee3ee --- /dev/null +++ b/apps/funding/views.py @@ -0,0 +1,99 @@ +# -*- 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 +from .forms import DummyForm +from .models import Offer, Spent + + +def mix(*streams): + substreams = [] + for stream, read_date, tag in streams: + iterstream = iter(stream) + try: + item = next(iterstream) + except StopIteration: + pass + else: + substreams.append([read_date(item), item, iterstream, read_date, tag]) + while substreams: + i, substream = max(enumerate(substreams), key=lambda x: x[1][0]) + yield substream[4], substream[1] + try: + item = next(substream[2]) + except StopIteration: + del substreams[i] + else: + substream[0:2] = [substream[3](item), item] + + +class WLFundView(TemplateView): + template_name = "funding/wlfund.html" + + def get_context_data(self): + def add_total(total, it): + for tag, e in it: + e.total = total + if tag == 'spent': + total += e.amount + else: + total -= e.wlfund + yield tag, e + + ctx = super(WLFundView, self).get_context_data() + offers = [] + for o in Offer.objects.all(): + if o.state() == 'lose': + o.wlfund = o.sum() + if o.wlfund > 0: + offers.append(o) + elif o.state() == 'win': + o.wlfund = o.sum() - o.target + if o.wlfund > 0: + offers.append(o) + amount = sum(o.wlfund for o in offers) - sum(o.amount for o in Spent.objects.all()) + print offers + + ctx['amount'] = amount + ctx['log'] = add_total(amount, mix( + (offers, lambda x: x.end, 'offer'), + (Spent.objects.all(), lambda x: x.timestamp, 'spent'), + )) + return ctx + + +class OfferDetailView(FormView): + form_class = DummyForm + template_name = "funding/offer_detail.html" + + def dispatch(self, request, slug): + self.object = get_object_or_404(Offer.public(), slug=slug) + return super(OfferDetailView, self).dispatch(request, slug) + + def get_form(self, form_class): + if self.request.method == 'POST': + return form_class(self.object, self.request.POST) + else: + 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) + ctx['object'] = self.object + return ctx + + def form_valid(self, form): + form.save() + return redirect(reverse("funding_thanks")) + + +class ThanksView(TemplateView): + template_name = "funding/thanks.html" + + def get_context_data(self, *args, **kwargs): + ctx = super(ThanksView, self).get_context_data(*args, **kwargs) + ctx['object'] = Offer.current() + return ctx 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/oai/handlers.py b/apps/oai/handlers.py index 59e599c98..eabb59c9f 100644 --- a/apps/oai/handlers.py +++ b/apps/oai/handlers.py @@ -12,6 +12,7 @@ from lxml.etree import ElementTree from django.db.models import Q from django.conf import settings from django.contrib.sites.models import Site +from django.utils import timezone WL_DC_READER_XPATH = '(.|*)/rdf:RDF/rdf:Description/%s/text()' @@ -60,7 +61,7 @@ class Catalogue(common.ResumptionOAIPMH): self.oai_id = "oai:" + Site.objects.get_current().domain + ":%s" # earliest change - year_zero = datetime(1990, 1, 1, 0, 0, 0) + year_zero = timezone.make_aware(datetime(1990, 1, 1, 0, 0, 0), timezone.utc) try: earliest_change = \ diff --git a/apps/pdcounter/static/pdcounter/pdcounter.js b/apps/pdcounter/static/pdcounter/pdcounter.js index 0352076b8..661fc9e14 100755 --- a/apps/pdcounter/static/pdcounter/pdcounter.js +++ b/apps/pdcounter/static/pdcounter/pdcounter.js @@ -1,8 +1,7 @@ (function($) { $(function() { - - $('#countdown').each(function() { + $('.countdown').each(function() { var $this = $(this); var serverTime = function() { @@ -24,13 +23,19 @@ $.countdown.setDefaults($.countdown.regional['']); } - var d = new Date($this.attr('data-year'), 0, 1); - function re() {location.reload()}; - $this.countdown({until: d, format: 'ydHMS', serverSync: serverTime, - onExpiry: re, alwaysExpire: true}); - + var options = { + until: new Date($this.attr('data-until')), + format: 'ydHMS', + serverSync: serverTime, + onExpiry: function(){location.reload()}, // TODO: no reload + }; + if ($this.hasClass('inline')) { + options.layout = '{dn} {dl} {hnn}{sep}{mnn}{sep}{snn}'; + } + + $this.countdown(options); }); }); -})(jQuery); \ No newline at end of file +})(jQuery); diff --git a/apps/pdcounter/templates/pdcounter/author_detail.html b/apps/pdcounter/templates/pdcounter/author_detail.html index 3810a4578..352b2050d 100644 --- a/apps/pdcounter/templates/pdcounter/author_detail.html +++ b/apps/pdcounter/templates/pdcounter/author_detail.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load i18n %} +{% load time_tags %} {% block titleextra %}{{ author.name }}{% endblock %} @@ -40,7 +41,7 @@ {% else %}

{% trans "This author's works will become part of public domain and will be allowed to be published without restrictions in" %}

-
+

{% trans "Find out why Internet libraries can't publish this author's works." %}

{% endif %} diff --git a/apps/pdcounter/templates/pdcounter/book_stub_detail.html b/apps/pdcounter/templates/pdcounter/book_stub_detail.html index f76edd17a..03ba8fa16 100644 --- a/apps/pdcounter/templates/pdcounter/book_stub_detail.html +++ b/apps/pdcounter/templates/pdcounter/book_stub_detail.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load i18n %} +{% load time_tags %} {% block titleextra %}{{ book.title }}{% endblock %} @@ -17,7 +18,7 @@ {% else %} {% if book.pd %}

{% trans "This work will become part of public domain and will be allowed to be published without restrictions in" %}

-
+

{% trans "Find out why Internet libraries can't publish this work." %}

{% else %}

{% trans "This work is copyrighted." %} diff --git a/apps/pdcounter/templatetags/__init__.py b/apps/pdcounter/templatetags/__init__.py new file mode 100755 index 000000000..e69de29bb diff --git a/apps/pdcounter/templatetags/time_tags.py b/apps/pdcounter/templatetags/time_tags.py new file mode 100755 index 000000000..1d4f23a93 --- /dev/null +++ b/apps/pdcounter/templatetags/time_tags.py @@ -0,0 +1,21 @@ +import datetime +import pytz +from django.conf import settings +from django import template +from django.utils import timezone + + +register = template.Library() + +@register.filter +def local_to_utc(localtime): + if isinstance(localtime, datetime.date): + localtime = datetime.datetime.combine(localtime, datetime.time(0,0)) + return timezone.utc.normalize( + pytz.timezone(settings.TIME_ZONE).localize(localtime) + ) + + +@register.filter +def utc_for_js(dt): + return dt.strftime('%Y/%m/%d %H:%M:%S UTC') diff --git a/apps/pdcounter/views.py b/apps/pdcounter/views.py index b07ee11e9..8fb1b13fa 100644 --- a/apps/pdcounter/views.py +++ b/apps/pdcounter/views.py @@ -2,8 +2,7 @@ # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # -#from datetime import datetime - +from datetime import datetime from django.template import RequestContext from django.shortcuts import render_to_response, get_object_or_404 from pdcounter import models @@ -12,7 +11,7 @@ from suggest.forms import PublishingSuggestForm def book_stub_detail(request, slug): book = get_object_or_404(models.BookStub, slug=slug) - pd_counter = book.pd + pd_counter = datetime(book.pd, 1, 1) form = PublishingSuggestForm( initial={"books": u"%s — %s, \n" % (book.author, book.title)}) @@ -23,7 +22,7 @@ def book_stub_detail(request, slug): def author_detail(request, slug): author = get_object_or_404(models.Author, slug=slug) - pd_counter = author.goes_to_pd() + pd_counter = datetime(author.goes_to_pd(), 1, 1) form = PublishingSuggestForm(initial={"books": author.name + ", \n"}) 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/apps/wolnelektury_core/static/css/header.css b/apps/wolnelektury_core/static/css/header.css index 633e8bde8..f1bd0df94 100755 --- a/apps/wolnelektury_core/static/css/header.css +++ b/apps/wolnelektury_core/static/css/header.css @@ -25,6 +25,9 @@ color: #989898; } +#logo a, #logo img { + display: block; +} #tagline { margin-left: 1.5em; diff --git a/apps/wolnelektury_core/static/img/green-pixel.png b/apps/wolnelektury_core/static/img/green-pixel.png new file mode 100644 index 000000000..edb512d11 Binary files /dev/null and b/apps/wolnelektury_core/static/img/green-pixel.png differ diff --git a/apps/wolnelektury_core/templates/superbase.html b/apps/wolnelektury_core/templates/superbase.html index 182d2b096..2aa653a7e 100644 --- a/apps/wolnelektury_core/templates/superbase.html +++ b/apps/wolnelektury_core/templates/superbase.html @@ -2,7 +2,7 @@ {% load cache compressed i18n %} {% load static from staticfiles %} - {% load catalogue_tags reporting_stats sponsor_tags %} + {% load catalogue_tags funding_tags reporting_stats sponsor_tags %} @@ -121,9 +121,8 @@ -

- + {% funding link=1 %}