From: Radek Czajka Date: Tue, 31 Mar 2026 12:38:09 +0000 (+0200) Subject: move progress to campaigns X-Git-Url: https://git.mdrn.pl/wolnelektury.git/commitdiff_plain/df6d939e24b62fb0424a0793e6ded1317a5b4915?ds=inline;hp=0df4ebc27bdd7b5d5dbd53fba72ab156f399e4c1 move progress to campaigns --- diff --git a/src/annoy/admin.py b/src/annoy/admin.py index ed651b2c5..74a4dbc49 100644 --- a/src/annoy/admin.py +++ b/src/annoy/admin.py @@ -1,19 +1,38 @@ +from django.db.models import Q from django.contrib import admin from django import forms +from django.utils.timezone import now from admin_ordering.admin import OrderableAdmin from modeltranslation.admin import TranslationAdmin +from wolnelektury.utils import YesNoFilter from . import models + admin.site.register(models.Campaign) +class IsCurrentFilter(YesNoFilter): + title = 'Aktualny' + parameter_name = 'current' + + @property + def q(self): + n = now() + return ~(Q(since__gt=n) | Q(until__lt=n) | Q(campaign__start__gt=n) | Q(campaign__end__lt=n)) + + class BannerAdmin(TranslationAdmin): list_display = [ 'place', 'text', - 'text_color', 'background_color', - 'priority', 'since', 'until', + 'campaign', + 'since', 'until', 'show_members', 'staff_preview', 'only_authenticated'] + list_filter = [ + 'campaign', + IsCurrentFilter, + ] + autocomplete_fields = ['books'] admin.site.register(models.Banner, BannerAdmin) diff --git a/src/annoy/migrations/0020_campaign_end_campaign_landing_campaign_priority_and_more.py b/src/annoy/migrations/0020_campaign_end_campaign_landing_campaign_priority_and_more.py new file mode 100644 index 000000000..611fc7936 --- /dev/null +++ b/src/annoy/migrations/0020_campaign_end_campaign_landing_campaign_priority_and_more.py @@ -0,0 +1,53 @@ +# Generated by Django 4.0.8 on 2026-03-31 11:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('annoy', '0019_campaign_banner_books_alter_banner_place_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='campaign', + name='end', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='campaign', + name='landing', + field=models.CharField(blank=True, max_length=1024), + ), + migrations.AddField( + model_name='campaign', + name='priority', + field=models.PositiveSmallIntegerField(default=0, help_text='Kampanie z wyższym priorytetem mają pierwszeństwo.', verbose_name='priorytet'), + ), + migrations.AddField( + model_name='campaign', + name='progress', + field=models.IntegerField(blank=True, null=True, verbose_name='postęp'), + ), + migrations.AddField( + model_name='campaign', + name='start', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='campaign', + name='target', + field=models.IntegerField(blank=True, null=True, verbose_name='cel'), + ), + 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'), ('seasonal', 'Sezonowa')], verbose_name='miejsce'), + ), + migrations.AlterField( + model_name='campaign', + name='name', + field=models.CharField(help_text='Wewnętrzna nazwa kampanii', max_length=255), + ), + ] diff --git a/src/annoy/migrations/0021_campaign_data.py b/src/annoy/migrations/0021_campaign_data.py new file mode 100644 index 000000000..a6a2d2b10 --- /dev/null +++ b/src/annoy/migrations/0021_campaign_data.py @@ -0,0 +1,41 @@ +# Generated by Django 4.0.8 on 2026-03-31 11:10 + +from django.db import migrations + +def data_campaign(apps, schema_editor): + Banner = apps.get_model('annoy', 'Banner') + Campaign = apps.get_model('annoy', 'Campaign') + for b in Banner.objects.exclude(target=None): + if b.campaign is None: + b.campaign = Campaign.objects.create(name='auto') + b.save() + c = b.campaign + c.target = b.target + c.progress = b.progress + c.start = b.since + c.end = b.until + c.save() + + +def data_campaign_rev(apps, schema_editor): + Banner = apps.get_model('annoy', 'Banner') + Campaign = apps.get_model('annoy', 'Campaign') + for c in Campaign.objects.exclude(target=None): + for b in c.banner_set.all(): + b.target = c.target + b.progress = c.progress + b.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('annoy', '0020_campaign_end_campaign_landing_campaign_priority_and_more'), + ] + + operations = [ + migrations.RunPython( + data_campaign, + data_campaign_rev + ) + ] diff --git a/src/annoy/migrations/0022_remove_banner_progress_remove_banner_target.py b/src/annoy/migrations/0022_remove_banner_progress_remove_banner_target.py new file mode 100644 index 000000000..d74b73ae5 --- /dev/null +++ b/src/annoy/migrations/0022_remove_banner_progress_remove_banner_target.py @@ -0,0 +1,21 @@ +# Generated by Django 4.0.8 on 2026-03-31 11:21 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('annoy', '0021_campaign_data'), + ] + + operations = [ + migrations.RemoveField( + model_name='banner', + name='progress', + ), + migrations.RemoveField( + model_name='banner', + name='target', + ), + ] diff --git a/src/annoy/models.py b/src/annoy/models.py index e1604bbad..175797ce1 100644 --- a/src/annoy/models.py +++ b/src/annoy/models.py @@ -9,12 +9,62 @@ from .places import PLACES, PLACE_CHOICES, STYLES class Campaign(models.Model): - name = models.CharField(max_length=255, help_text='Dla zespołu') + name = models.CharField(max_length=255, help_text='Wewnętrzna nazwa kampanii') image = models.FileField('obraz', upload_to='annoy/banners/', blank=True) + start = models.DateTimeField(null=True, blank=True) + end = models.DateTimeField(null=True, blank=True) + landing = models.CharField(blank=True, max_length=1024) + target = models.IntegerField('cel', null=True, blank=True) + progress = models.IntegerField('postęp', null=True, blank=True) + priority = models.PositiveSmallIntegerField( + 'priorytet', default=0, + help_text='Kampanie z wyższym priorytetem mają pierwszeństwo.') def __str__(self): return self.name + @classmethod + def get_active(cls): + n = now() + return cls.objects.exclude(start__gt=n).exclude(end__lt=n) + + def has_progress(self): + return self.target is not None and self.start is not None and self.end is not None + + @property + def progress_percent(self): + if not self.target: + return 0 + return (self.progress or 0) / self.target * 100 + + @property + def progress_percent_pretty(self): + return int(self.progress_percent) + + def update_progress(self): + if not self.start or not self.end or not self.target: + return + Schedule = apps.get_model('club', 'Schedule') + PayUOrder = apps.get_model('club', 'PayUOrder') + progress = PayUOrder.objects.filter( + completed_at__gte=self.start, + completed_at__lte=self.end, + ).aggregate(c=models.Sum('schedule__amount'))['c'] or 0 + + for schedule in Schedule.objects.filter( + method='paypal', + expires_at__gt=self.start + ): + progress += schedule.n_paypal_payments(self.start, self.end) * schedule.amount + self.progress = progress + self.save(update_fields=['progress']) + + @classmethod + def update_all_progress(cls): + n = now() + for obj in cls.get_active().exclude(target=None): + obj.update_progress() + class Banner(models.Model): place = models.SlugField('miejsce', choices=PLACE_CHOICES) @@ -44,8 +94,6 @@ class Banner(models.Model): 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) staff_preview = models.BooleanField('podgląd tylko dla zespołu', default=False) only_authenticated = models.BooleanField('tylko dla zalogowanych', default=False) @@ -67,6 +115,12 @@ class Banner(models.Model): else: return self.image + def get_url(self): + if self.url: + return self.url + if self.campaign: + return self.campaign.landing + def is_external(self): return (self.url and not self.url.startswith('/') and @@ -86,11 +140,15 @@ class Banner(models.Model): n = now() banners = cls.objects.filter( place=place + ).exclude( + campaign__start__gt=n + ).exclude( + campaign__end__lt=n ).exclude( since__gt=n ).exclude( until__lt=n - ).order_by('-priority', '?') + ).order_by('-campaign__priority', '-priority', '?') if book is None: banners = banners.filter(books=None) @@ -109,38 +167,6 @@ class Banner(models.Model): return banners - @property - def progress_percent(self): - if not self.target: - return 0 - return (self.progress or 0) / self.target * 100 - - @property - def progress_percent_pretty(self): - return int(self.progress_percent) - - def update_progress(self): - if not self.since or not self.until or not self.target: - return - Schedule = apps.get_model('club', 'Schedule') - PayUOrder = apps.get_model('club', 'PayUOrder') - progress = PayUOrder.objects.filter( - completed_at__gte=self.since, - completed_at__lte=self.until, - ).aggregate(c=models.Sum('schedule__amount'))['c'] or 0 - - for schedule in Schedule.objects.filter( - method='paypal', - expires_at__gt=self.since - ): - progress += schedule.n_paypal_payments(self.since, self.until) * schedule.amount - self.progress = progress - self.save(update_fields=['progress']) - - @classmethod - def update_all_progress(cls): - for obj in cls.objects.exclude(target=None).exclude(until__lt=now()): - obj.update_progress() class DynamicTextInsert(models.Model): diff --git a/src/annoy/signals.py b/src/annoy/signals.py index c98528247..5618996d3 100644 --- a/src/annoy/signals.py +++ b/src/annoy/signals.py @@ -13,6 +13,6 @@ def update_etag(sender, instance, **kwargs): @receiver(post_save, sender=club.models.Schedule) def update_progress(sender, instance, **kwargs): try: - models.Banner.update_all_progress() + models.Campaign.update_all_progress() except: pass diff --git a/src/annoy/templates/annoy/banner.html b/src/annoy/templates/annoy/banner.html index 9a67fcdcb..871fbba75 100644 --- a/src/annoy/templates/annoy/banner.html +++ b/src/annoy/templates/annoy/banner.html @@ -1,7 +1,7 @@ {% if banner %} {% if closable %} {{ banner.open_label }} @@ -22,7 +22,7 @@ {% if not banner.action_label %} + href="{{ banner.get_url }}"> {% endif %}
@@ -38,7 +38,7 @@ {% if banner.action_label %} + href="{{ banner.get_url }}"> {{ banner.action_label }} {% endif %} diff --git a/src/annoy/templates/annoy/banner_blackout.html b/src/annoy/templates/annoy/banner_blackout.html index c96751f38..c5a5e3da3 100644 --- a/src/annoy/templates/annoy/banner_blackout.html +++ b/src/annoy/templates/annoy/banner_blackout.html @@ -1,7 +1,7 @@ {% if banner %} {% if closable %} @@ -23,7 +23,7 @@
{{ banner.get_text|safe|linebreaks }}
- + {{ banner.action_label }} {{ banner.close_label|default:"x" }} diff --git a/src/annoy/templates/annoy/banner_crisis.html b/src/annoy/templates/annoy/banner_crisis.html index 9feffbf5f..22f52b092 100644 --- a/src/annoy/templates/annoy/banner_crisis.html +++ b/src/annoy/templates/annoy/banner_crisis.html @@ -31,25 +31,27 @@ {{ banner.get_text|safe|linebreaks }}
+ {% if banner.campaign.has_progress %}
- +
-   +  
{% if banner.action_label %} - + {{ banner.action_label }} {% endif %}
+ {% endif %}
diff --git a/src/annoy/templates/annoy/banner_seasonal.html b/src/annoy/templates/annoy/banner_seasonal.html index 792efc0fb..7f9dbf696 100644 --- a/src/annoy/templates/annoy/banner_seasonal.html +++ b/src/annoy/templates/annoy/banner_seasonal.html @@ -19,7 +19,7 @@ {% if not banner.action_label %} + href="{{ banner.get_url }}"> {% endif %}
@@ -38,7 +38,7 @@ {% if banner.action_label %} + href="{{ banner.get_url }}"> {{ banner.action_label }} {% endif %} @@ -47,23 +47,23 @@ {% endif %} + {% if banner.campaign.has_progress %}
- +
-   +  
- {% if banner.target %} - {{ banner.target|floatformat:"g" }} zł - {% endif %} + {{ banner.campaign.target|floatformat:"g" }} zł
+ {% endif %}
diff --git a/src/annoy/templates/annoy/banner_top.html b/src/annoy/templates/annoy/banner_top.html index 2a62d1173..c66d31e7c 100644 --- a/src/annoy/templates/annoy/banner_top.html +++ b/src/annoy/templates/annoy/banner_top.html @@ -33,7 +33,7 @@
{% if banner.action_label %} - + {{ banner.action_label }} {% endif %} diff --git a/src/wolnelektury/static/js/main.js b/src/wolnelektury/static/js/main.js index b22451168..68d35fa24 100644 --- a/src/wolnelektury/static/js/main.js +++ b/src/wolnelektury/static/js/main.js @@ -579,8 +579,11 @@ }); $(".annoy-banner-clickable").each(function() { - $(this).click(() => { - $("a.action", this).click(); + let btn = $("a.action", this)[0]; + $(this).click((e) => { + if (e.target !== btn) { + btn.click(); + } }); });