from . import models
+admin.site.register(models.Campaign)
+
+
class BannerAdmin(TranslationAdmin):
list_display = [
'place', 'text',
--- /dev/null
+# Generated by Django 4.0.8 on 2025-11-25 15:13
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('catalogue', '0051_book_has_audio'),
+ ('annoy', '0018_alter_banner_style'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Campaign',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(help_text='Dla zespołu', max_length=255)),
+ ('image', models.FileField(blank=True, upload_to='annoy/banners/', verbose_name='obraz')),
+ ],
+ ),
+ migrations.AddField(
+ model_name='banner',
+ name='books',
+ field=models.ManyToManyField(blank=True, to='catalogue.book'),
+ ),
+ migrations.AlterField(
+ model_name='banner',
+ name='place',
+ field=models.SlugField(choices=[('top', 'U góry wszystkich stron'), ('book-page', 'Strona książki'), ('book-page-center', 'Strona książki, środek'), ('book-text-intermission', 'Przerwa w treści książki'), ('book-fragment-list', 'Obok listy fragmentów książki'), ('blackout', 'Blackout'), ('crisis', 'Kryzysowa')], verbose_name='miejsce'),
+ ),
+ migrations.AddField(
+ model_name='banner',
+ name='campaign',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='annoy.campaign'),
+ ),
+ ]
from .places import PLACES, PLACE_CHOICES, STYLES
+class Campaign(models.Model):
+ name = models.CharField(max_length=255, help_text='Dla zespołu')
+ image = models.FileField('obraz', upload_to='annoy/banners/', blank=True)
+
+ def __str__(self):
+ return self.name
+
+
class Banner(models.Model):
place = models.SlugField('miejsce', choices=PLACE_CHOICES)
+ campaign = models.ForeignKey(Campaign, models.PROTECT, null=True, blank=True)
+
style = models.CharField(
'styl', max_length=255, blank=True,
choices=STYLES,
help_text='Bannery z wyższym priorytetem mają pierwszeństwo.')
since = models.DateTimeField('od', null=True, blank=True)
until = models.DateTimeField('do', null=True, blank=True)
+ books = models.ManyToManyField('catalogue.Book', blank=True)
+
target = models.IntegerField('cel', null=True, blank=True)
progress = models.IntegerField('postęp', null=True, blank=True)
show_members = models.BooleanField('widoczny dla członków klubu', default=False)
def get_text(self):
return Template(self.text).render(Context())
+ def get_image(self):
+ if self.campaign and self.campaign.image:
+ return self.campaign.image
+ else:
+ return self.image
+
+ def is_external(self):
+ return (self.url and
+ not self.url.startswith('/') and
+ not self.url.startswith('https://wolnelektury.pl/')
+ )
+
@classmethod
- def choice(cls, place, request, exemptions=True):
+ def choice(cls, place, request, exemptions=True, book=None):
Membership = apps.get_model('club', 'Membership')
if exemptions and hasattr(request, 'annoy_banner_exempt'):
until__lt=n
).order_by('-priority', '?')
+ if book is None:
+ banners = banners.filter(books=None)
+ else:
+ banners = banners.filter(models.Q(books=None) | models.Q(books=book))
+
+
if not request.user.is_authenticated:
banners = banners.filter(only_authenticated=False)
PLACE_DEFINITIONS = [
('top', 'U góry wszystkich stron', True),
('book-page', 'Strona książki', False),
+ ('book-page-center', 'Strona książki, środek', False),
('book-text-intermission', 'Przerwa w treści książki', False),
('book-fragment-list', 'Obok listy fragmentów książki', False),
('blackout', 'Blackout', True, (
annoy-banner
annoy-banner_{{ banner.place }}
annoy-banner-style_{{ banner.style }}
- {% if banner.image %}with-image{% endif %}
+ {% if banner.get_image %}with-image{% endif %}
{% if banner.smallfont %}banner-smallfont{% endif %}
"
id="annoy-banner-{{ banner.id }}"
{% if banner.background_color %}background-color: {{ banner.background_color }};{% endif %}
">
{% if not banner.action_label %}
- <a href="{{ banner.url }}">
+ <a
+ {% if banner.is_external %}target="_blank"{% endif %}
+ href="{{ banner.url }}">
{% endif %}
<div class="annoy-banner-inner">
- {% if banner.image %}
- <img src="{{ banner.image.url }}">
+ {% if banner.get_image %}
+ <div>
+ <img src="{{ banner.get_image.url }}">
+ </div>
{% endif %}
<div class="text">
{{ banner.get_text|safe|linebreaks }}
</div>
{% if banner.action_label %}
- <a class="action" href="{{ banner.url }}">
+ <a class="action"
+ {% if banner.is_external %}target="_blank"{% endif %}
+ href="{{ banner.url }}">
{{ banner.action_label }}
</a>
{% endif %}
@register.inclusion_tag('annoy/banner.html', takes_context=True)
-def annoy_banner(context, place):
- banners = Banner.choice(place, request=context['request'])
+def annoy_banner(context, place, **kwargs):
+ banners = Banner.choice(place, request=context['request'], **kwargs)
return {
'banner': banners.first(),
'closable': PLACES.get(place, False),
{% load choose_cites from social_tags %}
{% load catalogue_tags %}
{% load likes_book from social_tags %}
+{% load annoy %}
{% block global-content %}
+<div class="l-container">
+ {% annoy_banner 'book-page' %}
+ </div>
+
<div class="l-container">
<div class="l-breadcrumb">
<a href="/"><span>{% trans "Strona główna" %}</span></a>
<button class="l-article__read-more" aria-label="{% trans 'Kliknij aby rozwinąć' %}" data-label="{% trans 'Czytaj więcej' %}" data-action="{% trans 'Zwiń tekst' %}">{% trans 'Czytaj więcej' %}</button>
</article>
{% if accessible %}
- <div class="c-support">
- <div>
- <h2>
- {% blocktrans trimmed %}
- Ta książka jest dostępna dla tysięcy dzieciaków dzięki
- <span>darowiznom</span> od osób takich jak <span>Ty</span>!
- {% endblocktrans %}
- </h2>
- <a href="{% url 'club_join' %}?pk_campaign=layout">{% trans "Dorzuć się!" %}</a>
- </div>
- <div class="bg">
- <!-- img src="{% static '2022/images/dziecko.jpeg' %}" alt="Dorzuć się!" -->
- </div>
- </div>
+ {% annoy_banner 'book-page-center' book=book %}
{% endif %}
</div>
{% endwith %}
from fnpdjango.actions import export_as_csv_action
from modeltranslation.admin import TranslationAdmin
import annoy.models
+from messaging.models import Contact, Level
from wolnelektury.utils import YesNoFilter
from . import models
)
+class OptOutFilter(YesNoFilter):
+ title = 'opt out'
+ parameter_name = 'optout'
+ q = Q(email__in=Contact.objects.filter(level=Level.OPT_OUT).values_list('email', flat=True))
+
class ScheduleAdmin(admin.ModelAdmin):
form = ScheduleForm
search_fields = ['email', 'source']
list_filter = [
'is_cancelled', 'monthly', 'yearly', 'method',
+ 'consent', OptOutFilter,
PayedFilter, ActiveFilter, ExpiredFilter,
SourceFilter, CrisisFilter
]
self.referer = referer
super().__init__(*args, **kwargs)
club = models.Club.objects.first()
+ if self.instance.is_custom_amount():
+ self.fields['custom_amount'].initial = int(self.instance.amount)
if club is not None:
self.fields['custom_amount'].widget.attrs['min'] = club.min_amount
return state
def save(self, *args, **kwargs):
- self.instance.source = self.referer
+ if self.referer is not None:
+ self.instance.source = self.referer
return super().save(*args, **kwargs)
club = Club.objects.first()
return club.get_description_for_amount(self.amount, self.monthly)
+ def is_custom_amount(self):
+ club = Club.objects.first()
+ if not self.amount:
+ return False
+ if self.monthly:
+ return not club.monthlyamount_set.filter(amount=self.amount).exists()
+ else:
+ return not club.singleamount_set.filter(amount=self.amount).exists()
+
def initiate_payment(self, request):
return self.get_payment_method().initiate(request, self)
--- /dev/null
+<div class="checkout-infobar">
+ <div class="if-monthly">
+ Dziękujemy, że decydujesz się wspierać nas co miesiąc.<br/>
+ Jeśli to pomyłka, możesz zmienić darowiznę na <a class="donation-mod-monthly" data-url="{% url 'donation_set_monthly' schedule.key %}" data-monthly="false" href="{% url 'donation_step1' schedule.key %}">jednorazową</a>.
+ </div>
+ <div class="if-not-monthly">
+ Wolę wspierać co miesiąc!
+ <a class="donation-mod-monthly" data-url="{% url 'donation_set_monthly' schedule.key %}" data-monthly="true" href="{% url 'donation_step1' schedule.key %}">Zmień na comiesięczną wpłatę.</a>
+ </div>
+</div>
{% load static %}
{% load i18n %}
-<form method="post" action="{% url 'club_join' %}">
+<form method="post" action="{% if schedule %}{% url 'donation_step1' key=schedule.key %}{% else %}{% url 'club_join' %}{% endif %}">
{% csrf_token %}
{{ form.errors }}
<input type="radio" name="switch" id="switch-once" value="single" class="toggle-input" {% if schedule and not schedule.monthly %}checked{% endif %}>
{% with amounts=club.get_amounts %}
<div class="l-checkout__payments payments-once wide-spot-{{ amounts.single_wide_spot }}">
{% for amount in amounts.single %}
- <div class="l-checkout__payments__box once{% if not schedule.monthly and schedule.amount == amount.amount or not schedule and club.default_single_amount == amount.amount %} is-active{% endif %}{% if amount.wide %} l-checkout__payments__box--special{% endif %} l-checkout__payments__box--{{ amount.box_variant }}">
+ <div class="l-checkout__payments__box once{% if not schedule.monthly and schedule.amount == amount.amount or not schedule and club.default_single_amount == amount.amount %} is-active initial-active{% endif %}{% if amount.wide %} l-checkout__payments__box--special{% endif %} l-checkout__payments__box--{{ amount.box_variant }}">
- <h3>{{ amount.amount }} zł</h3>
<div class="l-checkout__payments__box__btn-wrp">
- {% if amount.description %}
- <p>{{ amount.description|safe }}</p>
- {% endif %}
- <button name="single_amount" value="{{ amount.amount }}">{% trans "Wybierz" %}</button>
+ <button name="single_amount" value="{{ amount.amount }}">{{ amount.amount }} zł</button>
</div>
</div>
{% endfor %}
<div class="l-checkout__payments payments-recurring wide-spot-{{ amounts.monthly_wide_spot }}">
{% for amount in amounts.monthly %}
- <div class="l-checkout__payments__box{% if schedule.monthly and schedule.amount == amount.amount or not schedule and amount.amount == club.default_monthly_amount %} is-active{% endif %}{% if amount.wide %} l-checkout__payments__box--special{% endif %} l-checkout__payments__box--{{ amount.box_variant }}">
- <h3>{{ amount.amount }} zł <span>{% trans "/mies." context "kwota na miesiąc" %}</span></h3>
+ <div class="l-checkout__payments__box{% if schedule.monthly and schedule.amount == amount.amount or not schedule and amount.amount == club.default_monthly_amount %} is-active initial-active{% endif %}{% if amount.wide %} l-checkout__payments__box--special{% endif %} l-checkout__payments__box--{{ amount.box_variant }}">
<div class="l-checkout__payments__box__btn-wrp">
- {% if amount.description %}
- <p>{{ amount.description|safe }}</p>
- {% endif %}
- <button name="monthly_amount" value="{{ amount.amount }}">{% trans "Wybierz" %}</button>
+ <button name="monthly_amount" value="{{ amount.amount }}">{{ amount.amount }} zł <span> /mies.</span></button>
</div>
</div>
{% endfor %}
<div class="l-checkout__amount">
<div class="l-checkout__input">
- <label for="kwota">{% trans "Inna kwota" %}</label>
+ <label for="id_custom_amount">{% trans "Inna kwota" %}</label>
{{ form.custom_amount }}
</div>
<button>{% trans "Dalej" %}</button>
{% block donation-step-content %}
- <div class="l-checkout__cols">
+ <div class="l-checkout__cols q-is-monthly {% if schedule.monthly %}is-monthly{% endif %}">
<div class="l-checkout__col">
<div class="l-checkout__payments__box is-active">
<h3>
{{ schedule.amount|floatformat }} zł
- {% if schedule.monthly %}
- <span>{% trans "/mies." context "kwota na miesiąc" %}</span>
- {% endif %}
+ <span class="if-monthly">{% trans "miesięcznie" %}</span>
+ <span class="if-not-monthly">{% trans "jednorazowo" %}</span>
</h3>
<img src="{% static '2022/images/checkout-img-3.jpg' %}" alt="">
+ {% if schedule.get_description %}
<p>{{ schedule.get_description }}</p>
+ {% endif %}
</div>
</div>
<div class="l-checkout__col">
+ {% include "club/donation_infobox.html" %}
+
<form method='post'>
{% csrf_token %}
{{ form.errors }}
- {{ form.amount }}
- {{ form.monthly }}
<div class="l-checkout__form">
<div class="l-checkout__form__row">
<div class="l-checkout__input">
{% block donation-step-content %}
- <div class="l-checkout__cols">
+ <div class="l-checkout__cols q-reload-is-monthly {% if schedule.monthly %}is-monthly{% endif %}">
<div class="l-checkout__col">
<div class="l-checkout__payments__box is-active">
<h3>
{{ schedule.amount|floatformat }} zł
{% if schedule.monthly %}
- <span>{% trans "/mies." context "kwota na miesiąc" %}</span>
+ <span>{% trans "miesięcznie" %}</span>
+ {% else %}
+ <span>{% trans "jednorazowo" %}</span>
{% endif %}</h3>
<img src="{% static '2022/images/checkout-img-3.jpg' %}" alt="">
</div>
</div>
<div class="l-checkout__col">
+ {% include "club/donation_infobox.html" %}
+
+ {% if schedule.monthly %}
+ <h3>Darowizna będzie pobierana automatycznie co miesiąc.</h3>
+ <p>Możesz z niej zrezygnować w dowolnej chwili, korzystając z linku który dostaniesz mailem.</p>
+ {% endif %}
+
<div class="l-checkout__form">
<div class="l-checkout__form__row full">
- <div class="iframe">
+
+
+ <div class="iframe">
+
{% for method in schedule.get_payment_methods %}
{% invite_payment method schedule %}
{% endfor %}
<div class="l-checkout__box__header">
<img src="{% block donation-jumbo-image %}{% static '2022/images/checkout-img-1.jpg' %}{% endblock %}" alt="Wspieraj Wolne Lektury">
<div class="l-checkout__box__header__content">
- <h1>{% trans "Wspieraj Wolne Lektury" %}</h1>
- <p>{% trans "Dziękujemy, że chcesz razem z nami uwalniać książki!" %}</p>
- <p>{% trans "Wspieraj Wolne Lektury stałą wpłatą – nawet niewielka ma wielką moc! Możesz też wesprzeć Wolne Lektury jednorazowo." %}</p>
+ {% chunk "donate-top" %}
</div>
</div>
<div class="l-checkout__steps">
{% endif %}
<div class="{% if view.step == 1 %}is-current{% else %}is-completed{% endif %}">
<span>1</span>
- <p>{% trans "Rodzaj wsparcia" %}</p>
+ <p>{% trans "Kwota wsparcia" %}</p>
</div>
{% if view.step > 1 and view.step != 4 %}
</a>
<div class="l-checkout__footer">
<div class="l-checkout__footer__content">
+ {% chunk 'donate-bottom' %}
+
<div class="l-checkout__footer__content__item">
<h3>{% trans "Transparentność jest dla nas bardzo ważna." %}</h3>
<div>
{% load i18n static %}
-<h3>{% trans "Wolisz wpłacić przez PayPal?" %}</h3>
+<h3>
+ {% if schedule.monthly %}
+ {% trans "Wolisz ustawić comiesięczną darowiznę przez PayPal?" %}
+ {% else %}
+ {% trans "Wolisz wpłacić przez PayPal?" %}
+ {% endif %}
+</h3>
<a href="{% url 'paypal_init' schedule.key %}">
<div class="iframe">
<img src="{% static 'club/paypal.png' %}" alt="PayPal">
{% load i18n %}
-<h3>{% trans "Podaj dane karty płatniczej" %}</h3>
+<h3>{% trans "Podaj dane karty płatniczej do comiesięcznej darowizny" %}</h3>
<div class="iframe">
<form id="theform" method='POST' action='{% url "club_payu_rec_payment" schedule.key %}'>
{% csrf_token %}
path('plan/<key>/zestawienie/<int:year>/', banner_exempt(views.YearSummaryView.as_view()), name='club_year_summary'),
path('plan/<key>/rodzaj/', banner_exempt(views.DonationStep1.as_view()), name='donation_step1'),
path('plan/<key>/dane/', banner_exempt(views.DonationStep2.as_view()), name='donation_step2'),
+ path('plan/<key>/ustaw-miesiecznie/', views.set_monthly, name='donation_set_monthly'),
path('przylacz/<key>/', views.claim, name='club_claim'),
path('anuluj/<key>/', views.cancel, name='club_cancel'),
from django.conf import settings
from django.contrib.auth.decorators import login_required, permission_required
from django.db.models import Sum
-from django.http import HttpResponse, HttpResponseRedirect
+from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.utils.decorators import method_decorator
+@method_decorator(never_cache, name='dispatch')
class DonationStep1(UpdateView):
queryset = models.Schedule.objects.filter(payed_at=None)
form_class = forms.DonationStep1Form
return reverse('donation_step2', args=[self.object.key])
+@method_decorator(never_cache, name='dispatch')
class DonationStep2(UpdateView):
queryset = models.Schedule.objects.filter(payed_at=None)
form_class = forms.DonationStep2Form
return c
+def set_monthly(request, key):
+ schedule = get_object_or_404(models.Schedule, payed_at=None, key=key)
+ if request.POST:
+ schedule.monthly = request.POST.get('monthly') == 'true'
+ schedule.save(update_fields=['monthly'])
+ return JsonResponse({
+ "amount": schedule.amount,
+ "monthly": schedule.monthly,
+ })
+
+
class JoinView(CreateView):
form_class = forms.DonationStep1Form
template_name = 'club/donation_step1.html'
}
}
}
+
+
+
+.annoy-banner_book-page {
+ background-color: #ffd430;
+ color: #083F4D;
+ border-radius: 10px;
+ padding: 15px 20px;
+ margin-top: 20px;
+
+ .annoy-banner-inner {
+ display: flex;
+ flex-direction: row;
+ gap: 20px;
+ align-items: flex-start;
+ justify-content: space-between;
+
+ p {
+ margin: 0;
+ }
+ a {
+ line-height: 1.35;
+ color: #c32721;
+ white-space: nowrap;
+ border: solid #c32721;
+ border-width: 0 0 1px;
+
+ &:hover {
+ text-decoration: none;
+ border-bottom-width: 2px;
+ }
+ }
+ }
+}
+
+.annoy-banner_book-page-center {
+ background: white;
+ margin-top: 50px;
+ padding: 20px;
+ border: 1px solid #018189;
+ border-radius: 15px;
+ color:#018189;
+
+ .annoy-banner-inner {
+ display: flex;
+ gap: 15px;
+ align-items: center;
+
+ img {
+ width: 150px;
+ }
+ p {
+ margin: 0;
+ }
+ }
+}
}
}
button {
- height: 56px;
+ margin: 0;
+ font-family: "Source Sans Pro",sans-serif;
+ font-weight: bold;
+ font-size: 44px;
+ line-height: 130%;
+ letter-spacing: -0.01em;
+ height: 90px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all $ease-out 250ms;
+
background: #FFFFFF;
border: 1px solid #92BD39;
border-radius: 3px;
width: 100%;
outline: 0;
cursor: pointer;
- font-weight: 600;
- font-size: 16px;
- line-height: 24px;
- display: flex;
- align-items: center;
- justify-content: center;
text-align: center;
color: #083F4D;
- transition: background $ease-out 250ms;
- @include rwd($break-flow) {
- font-size: 20px;
- line-height: 25px;
+ span {
+ font-weight: 600;
+ font-size: 25px;
+ line-height: 200%;
+ letter-spacing: -0.01em;
+ color: #92BD39;
+ margin-left: 10px;
+ transition: opacity $ease-out 250ms;
}
&:hover {
&:hover {
background: #83AD2B;
}
+ span {
+ color: white;
+ }
}
}
}
}
}
+
+
+.if-monthly { display: none; }
+.is-monthly {
+ .if-monthly {
+ display: block;
+ }
+ .if-not-monthly {
+ display: none;
+ }
+}
+
+
+.checkout-infobar {
+ margin: 0 0 20px;
+ padding: 20px;
+ border-radius: 10px;
+ border: 1px solid #edc016;
+ background: #edc016;
+}
color: white;
}
}
+
+ .menubar-donate {
+ color: #fff;
+ background: #c92834;
+ padding: 9px 20px 11px;
+ font-weight: 600;
+ margin-right: 20px;
+ border-radius: 15px;
+ }
}
.user {
display: block;
left: 16px;
}
+
+ .menubar-donate {
+ display: none;
+ }
}
}
}
$('input', container).val($(this).val());
$('.is-active', container).removeClass('is-active');
$(this).closest('.l-checkout__payments__box').addClass('is-active');
- $('#kwota').val('');
- return false;
+ $('#id_custom_amount').val('');
+ });
+
+ $('#id_custom_amount').on('input', function() {
+ if ($(this).val() > 0) {
+ $('.l-checkout__payments__box.is-active').removeClass('is-active');
+ } else {
+ $('.l-checkout__payments__box.initial-active').addClass('is-active');
+ }
+ });
+
+ $('.donation-mod-monthly').on('click', function() {
+ $.ajax({
+ method: 'POST',
+ data: {
+ csrfmiddlewaretoken: $("[name=csrfmiddlewaretoken]").val(),
+ monthly: $(this).data('monthly'),
+ },
+ url: $(this).data('url'),
+ success: function(data) {
+ if ($(".q-reload-is-monthly").length) {
+ window.location.reload()
+ } else {
+ $(".q-is-monthly").toggleClass('is-monthly', data.monthly);
+ }
+ }
+ });
+ return false;
});
})();
</ul>
</div>
<a href="{% url 'user_settings' %}" class="user">
- {% if request.user.is_staffs %}
+ {% if request.user.is_staff %}
<img src="{% static '2022/images/icons/user-staff.svg' %}">
{% elif request.user.membership %}
<img src="{% static '2022/images/icons/user-vip.svg' %}">
{% else %}
<img src="{% static '2022/images/icons/user.svg' %}">
{% endif %}
- </a>
+ </a>
{% else %}
<div class="l-navigation__login">
<a id="login-link" href='{% url 'login' %}?next={{ request.path }}'>{% trans "Zaloguj się" %}</a>
<a href='{% url 'register' %}?next={{ request.path }}'>{% trans "Załóż konto" %}</a>
</div>
{% endif %}
+
+<a href="/pomagam/?pk_campaign=menubar" class="menubar-donate">Wspieram!</a>
+