banner fix
[wolnelektury.git] / src / annoy / models.py
1 import hashlib
2 import json
3 from django.apps import apps
4 from django.conf import settings
5 from django.db import models
6 from django.template import Context, Template
7 from django.utils.timezone import now
8 from .places import PLACES, PLACE_CHOICES, STYLES
9
10
11 class Campaign(models.Model):
12     name = models.CharField(max_length=255, help_text='Dla zespołu')
13     image = models.FileField('obraz', upload_to='annoy/banners/', blank=True)
14
15     def __str__(self):
16         return self.name
17
18
19 class Banner(models.Model):
20     place = models.SlugField('miejsce', choices=PLACE_CHOICES)
21     campaign = models.ForeignKey(Campaign, models.PROTECT, null=True, blank=True)
22
23     style = models.CharField(
24         'styl', max_length=255, blank=True,
25         choices=STYLES,
26     )
27     smallfont = models.BooleanField('mały font', default=False)
28     text_color = models.CharField(max_length=10, blank=True)
29     background_color = models.CharField(max_length=10, blank=True)
30     action_label = models.CharField(
31         'etykieta akcji',
32         max_length=255, blank=True,
33         help_text='Jeśli pusta, cały banner będzie służył jako link.'
34     )
35     open_label = models.CharField('etykieta otwierania', max_length=255, blank=True)
36     close_label = models.CharField('etykieta zamykania', max_length=255, blank=True)
37     text = models.TextField('tekst')
38     image = models.FileField('obraz', upload_to='annoy/banners/', blank=True)
39     url = models.CharField('URL', max_length=1024)
40     priority = models.PositiveSmallIntegerField(
41         'priorytet', default=0,
42         help_text='Bannery z wyższym priorytetem mają pierwszeństwo.')
43     since = models.DateTimeField('od', null=True, blank=True)
44     until = models.DateTimeField('do', null=True, blank=True)
45     books = models.ManyToManyField('catalogue.Book', blank=True)
46
47     target = models.IntegerField('cel', null=True, blank=True)
48     progress = models.IntegerField('postęp', null=True, blank=True)
49     show_members = models.BooleanField('widoczny dla członków klubu', default=False)
50     staff_preview = models.BooleanField('podgląd tylko dla zespołu', default=False)
51     only_authenticated = models.BooleanField('tylko dla zalogowanych', default=False)
52
53     class Meta:
54         verbose_name = 'banner'
55         verbose_name_plural = 'bannery'
56         ordering = ('place', '-priority',)
57
58     def __str__(self):
59         return self.text
60
61     def get_text(self):
62         return Template(self.text).render(Context())
63
64     def get_image(self):
65         if self.campaign and self.campaign.image:
66             return self.campaign.image
67         else:
68             return self.image
69
70     def is_external(self):
71         return (self.url and
72                 not self.url.startswith('/') and
73                 not self.url.startswith('https://wolnelektury.pl/')
74                 )
75
76     @classmethod
77     def choice(cls, place, request, exemptions=True, book=None):
78         Membership = apps.get_model('club', 'Membership')
79
80         if exemptions and hasattr(request, 'annoy_banner_exempt'):
81             return cls.objects.none()
82
83         if settings.DEBUG:
84             assert place in PLACES, f"Banner place `{place}` must be defined in annoy.places."
85
86         n = now()
87         banners = cls.objects.filter(
88             place=place
89         ).exclude(
90             since__gt=n
91         ).exclude(
92             until__lt=n
93         ).order_by('-priority', '?')
94
95         if book is None:
96             banners = banners.filter(books=None)
97         else:
98             banners = banners.filter(models.Q(books=None) | models.Q(books=book))
99             
100         
101         if not request.user.is_authenticated:
102             banners = banners.filter(only_authenticated=False)
103
104         if not request.user.is_staff:
105             banners = banners.filter(staff_preview=False)
106
107         if Membership.is_active_for(request.user):
108             banners = banners.filter(show_members=True)
109
110         return banners
111
112     @property
113     def progress_percent(self):
114         if not self.target:
115             return 0
116         return (self.progress or 0) / self.target * 100
117
118     @property
119     def progress_percent_pretty(self):
120         return int(self.progress_percent)
121
122     def update_progress(self):
123         # Total of new payments during the action.
124         # This definition will need to change for longer timespans.
125         if not self.since or not self.until or not self.target:
126             return
127         Schedule = apps.get_model('club', 'Schedule')
128         self.progress = Schedule.objects.filter(
129             payed_at__gte=self.since,
130             payed_at__lte=self.until,
131         ).aggregate(c=models.Sum('amount'))['c']
132         self.save(update_fields=['progress'])
133
134     @classmethod
135     def update_all_progress(cls):
136         for obj in cls.objects.exclude(target=None):
137             obj.update_progress()
138
139
140 class DynamicTextInsert(models.Model):
141     paragraphs = models.IntegerField('akapity')
142     url = models.CharField(max_length=1024)
143
144     class Meta:
145         verbose_name = 'dynamiczna wstawka'
146         verbose_name_plural = 'dynamiczne wstawki'
147         ordering = ('paragraphs', )
148
149     def __str__(self):
150         return str(self.paragraphs)
151
152     @classmethod
153     def get_all(cls, request):
154         Membership = apps.get_model('club', 'Membership')
155         if Membership.is_active_for(request.user) and not request.user.is_staff:
156             return cls.objects.none()
157         return cls.objects.all()
158
159
160     def choose(self):
161         return self.dynamictextinserttext_set.order_by('?').first()
162
163
164 class DynamicTextInsertText(models.Model):
165     insert = models.ForeignKey(DynamicTextInsert, models.CASCADE)
166     own_colors = models.BooleanField(default=False)
167     background_color = models.CharField(max_length=10, blank=True)
168     text_color = models.CharField(max_length=10, blank=True)
169     text = models.TextField('tekst')
170     image = models.FileField(blank=True, upload_to='annoy/inserts/')
171
172
173 class MediaInsertSet(models.Model):
174     file_format = models.CharField(max_length=8, choices=[
175         ('epub', 'epub'),
176         ('mobi', 'mobi'),
177         ('pdf', 'pdf'),
178         ])
179     etag = models.CharField(max_length=64, blank=True)
180
181     def update_etag(self):
182         self.etag = hashlib.sha1(json.dumps(self.get_texts()).encode('utf-8')).hexdigest()
183         self.save(update_fields=['etag'])
184
185     def get_texts(self):
186         return [t.text for t in self.mediainserttext_set.all()]
187
188     @classmethod
189     def get_for_format(cls, file_format):
190         return cls.objects.filter(file_format=file_format).first()
191
192     @classmethod
193     def get_texts_for(cls, file_format):
194         self = cls.get_for_format(file_format)
195         if self is None:
196             return []
197         return self.get_texts()
198
199
200 class MediaInsertText(models.Model):
201     media_insert_set = models.ForeignKey(MediaInsertSet, models.CASCADE)
202     ordering = models.IntegerField()
203     text = models.TextField()
204
205     class Meta:
206         ordering = ('ordering',)