stats
[wolnelektury.git] / src / club / views.py
1 # This file is part of Wolne Lektury, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Wolne Lektury. See NOTICE for more information.
3 #
4 from datetime import date, timedelta
5 from django.conf import settings
6 from django.contrib.auth.decorators import login_required, permission_required
7 from django.db.models import Sum
8 from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
9 from django.shortcuts import get_object_or_404, render
10 from django.urls import reverse
11 from django.utils.decorators import method_decorator
12 from django.views.decorators.cache import never_cache
13 from django.views.generic import FormView, CreateView, TemplateView, DetailView, UpdateView
14 from django.views import View
15 from .payu import POSS
16 from .payu import views as payu_views
17 from .forms import PayUCardTokenForm
18 from . import forms
19 from . import models
20 from .helpers import get_active_schedule
21 from .payment_methods import recurring_payment_method
22
23
24 class ClubView(TemplateView):
25     template_name = 'club/index.html'
26
27     def get_context_data(self, *args, **kwargs):
28         ctx = super().get_context_data(*args, **kwargs)
29         ctx['active_menu_item'] = 'club'
30         return ctx
31
32
33
34 @method_decorator(never_cache, name='dispatch')
35 class DonationStep1(UpdateView):
36     queryset = models.Schedule.objects.filter(payed_at=None)
37     form_class = forms.DonationStep1Form
38     slug_field = slug_url_kwarg = 'key'
39     template_name = 'club/donation_step1.html'
40     step = 1
41
42     def get_context_data(self, **kwargs):
43         c = super().get_context_data(**kwargs)
44         c['club'] = models.Club.objects.first()
45         return c
46
47     def get_success_url(self):
48         return reverse('donation_step2', args=[self.object.key])
49
50
51 @method_decorator(never_cache, name='dispatch')
52 class DonationStep2(UpdateView):
53     queryset = models.Schedule.objects.filter(payed_at=None)
54     form_class = forms.DonationStep2Form
55     slug_field = slug_url_kwarg = 'key'
56     template_name = 'club/donation_step2.html'
57     step = 2
58
59     def get_context_data(self, **kwargs):
60         c = super().get_context_data(**kwargs)
61         c['club'] = models.Club.objects.first()
62         return c
63
64
65 def set_monthly(request, key):
66     schedule = get_object_or_404(models.Schedule, payed_at=None, key=key)
67     if request.POST:
68         schedule.monthly = request.POST.get('monthly') == 'true'
69         schedule.save(update_fields=['monthly'])
70     return JsonResponse({
71         "amount": schedule.amount,
72         "monthly": schedule.monthly,
73     })
74     
75
76 class JoinView(CreateView):
77     form_class = forms.DonationStep1Form
78     template_name = 'club/donation_step1.html'
79
80     @property
81     def club(self):
82         return models.Club.objects.first()
83
84     def is_app(self):
85         return self.request.GET.get('app')
86
87     def get(self, request):
88         if settings.CLUB_APP_HOST and self.is_app() and request.META['HTTP_HOST'] != settings.CLUB_APP_HOST:
89             return HttpResponseRedirect("https://" + settings.CLUB_APP_HOST + request.get_full_path())
90
91         if self.is_app():
92             request.session['from_app'] = True
93         elif request.session and 'from_app' in request.session:
94             del request.session['from_app']
95
96         return super().get(request)
97
98     def get_context_data(self, **kwargs):
99         c = super(JoinView, self).get_context_data(**kwargs)
100         c['membership'] = getattr(self.request.user, 'membership', None)
101         c['active_menu_item'] = 'club'
102         c['club'] = models.Club.objects.first()
103
104         c['ambassador'] = models.Ambassador.objects.all().order_by('?').first()
105         return c
106
107     def get_initial(self):
108         # referer?
109         if self.request.user.is_authenticated and self.request.user.email:
110             return {
111                 'email': self.request.user.email
112             }
113
114     def get_form_kwargs(self):
115         kwargs = super().get_form_kwargs()
116         kwargs['referer'] = self.request.META.get('HTTP_REFERER', '')
117         return kwargs
118
119     def form_valid(self, form):
120         retval = super(JoinView, self).form_valid(form)
121         if self.request.user.is_authenticated:
122             form.instance.membership, created = models.Membership.objects.get_or_create(user=self.request.user)
123             form.instance.save()
124         return retval
125
126     def get_success_url(self):
127         return reverse('donation_step2', args=[self.object.key])
128
129
130 @method_decorator(never_cache, name='dispatch')
131 class ScheduleView(DetailView):
132     queryset = models.Schedule.objects.exclude(email='')
133     slug_field = slug_url_kwarg = 'key'
134     template_name = 'club/schedule.html'
135     step = 3
136     
137     def get_template_names(self):
138         if not self.object.payed_at:
139             return 'club/donation_step3.html'
140         return 'club/schedule.html'
141         
142     def get_context_data(self, *args, **kwargs):
143         ctx = super().get_context_data(*args, **kwargs)
144         ctx['active_menu_item'] = 'club'
145         return ctx
146
147     def post(self, request, key):
148         schedule = self.get_object()
149         return HttpResponseRedirect(schedule.initiate_payment(request))
150
151
152 @login_required
153 def claim(request, key):
154     schedule = models.Schedule.objects.get(key=key, membership=None)
155     schedule.membership, created = models.Membership.objects.get_or_create(user=request.user)
156     schedule.save()
157     return HttpResponseRedirect(schedule.get_absolute_url())
158
159
160 def cancel(request, key):
161     schedule = models.Schedule.objects.get(key=key)
162     schedule.is_cancelled = True
163     schedule.save()
164     return HttpResponseRedirect(schedule.get_absolute_url())
165
166
167 class PayUPayment(DetailView):
168     model = models.Schedule
169     slug_field = slug_url_kwarg = 'key'
170
171     def get(self, request, key):
172         schedule = self.get_object()
173         schedule.method = 'payu'
174         schedule.save(update_fields=['method'])
175         return HttpResponseRedirect(schedule.initiate_payment(request))
176
177
178
179 class PayURecPayment(payu_views.RecPayment):
180     form_class = PayUCardTokenForm
181
182     def get_schedule(self):
183         return get_object_or_404(models.Schedule, key=self.kwargs['key'])
184
185     def get_pos(self):
186         pos_id = recurring_payment_method.pos_id
187         return POSS[pos_id]
188
189     def get_success_url(self):
190         schedule = self.get_schedule()
191         schedule.method = 'payu-re'
192         schedule.save(update_fields=['method'])
193         return schedule.pay(self.request)
194
195
196 class PayUNotifyView(payu_views.NotifyView):
197     order_model = models.PayUOrder
198
199
200 class ScheduleThanksView(DetailView):
201     model = models.Schedule
202     template_name = 'club/donation_step4.html'
203     slug_field = slug_url_kwarg = 'key'
204     step = 4
205
206     def get_context_data(self, *args, **kwargs):
207         ctx = super().get_context_data(*args, **kwargs)
208         ctx['active_menu_item'] = 'club'
209         return ctx
210
211
212 class YearSummaryView(DetailView):
213     model = models.Schedule
214     slug_field = slug_url_kwarg = 'key'
215     template_name = 'club/year_summary.html'
216
217     def get_context_data(self, *args, **kwargs):
218         ctx = super().get_context_data(*args, **kwargs)
219         ctx['payments'] = models.PayUOrder.objects.filter(
220             status='COMPLETED',
221             completed_at__year=self.kwargs['year'],
222             schedule__email=self.object.email,
223         ).order_by('completed_at')
224         ctx['total_amount'] = ctx['payments'].aggregate(s=Sum('schedule__amount'))['s']
225         return ctx
226
227
228 @permission_required('club.schedule_view')
229 def member_verify(request):
230     if request.method == 'POST':
231         emails = request.POST.get('emails').strip().split('\n')
232         rows = ['email;członek;nazwa użytkownika;aktywny;co najmniej do']
233         for email in emails:
234             email = email.strip()
235             row = [email]
236             schedules = models.Schedule.objects.filter(email=email).exclude(payed_at=None)
237             if schedules.exists():
238                 row.append('tak')
239                 akt = False
240                 unames = set()
241                 exp = None
242                 for s in schedules:
243                     if s.is_active():
244                         akt = True
245                     if s.membership:
246                         unames.add(s.membership.user.username) 
247                     if exp is None or s.expires_at > exp:
248                         exp = s.expires_at
249                 row.append(','.join(sorted(unames)))
250                 row.append('tak' if akt else 'nie')
251                 row.append(exp.date().isoformat())
252             else:
253                 row.append('nie')
254             rows.append(';'.join(row))
255         rows = '\n'.join(rows)
256     else:
257         rows = ''
258
259     return render(
260         request,
261         'club/member_verify.html',
262         {
263             'result': rows
264         }
265     )
266
267
268 @permission_required('club.schedule_view')
269 def receipt(request):
270     email = request.POST.get('email')
271     try:
272         year = int(request.POST.get('year'))
273     except:
274         return HttpResponse('no content')
275
276     receipt = models.PayUOrder.generate_receipt(email, year)
277     if receipt:
278         content, optout, payments = receipt
279     else:
280         return HttpResponse('no content')
281     return HttpResponse(
282         content,
283         headers={
284             "Content-Type": "application/pdf",
285             "Content-Disposition": f'attachment; filename="wolnelektury-{year}-{email}.pdf"',
286         }
287     )
288
289
290 @permission_required('club.schedule_view')
291 def stats(request):
292     acq = {}
293     today = date.today()
294     start = today - timedelta(365)
295     for schedule in models.Schedule.objects.filter(
296         payed_at__gte=start,
297     ):
298         d = schedule.payed_at.date()
299         m = schedule.method.replace('-', '_')
300         acq.setdefault(d, {})
301         acq[d].setdefault(m, 0)
302         acq[d][m] += schedule.amount
303         
304     days = []
305     d = today
306     while d >= start:
307         days.append((d.isoformat(), acq.get(d, {})))
308         d -= timedelta(1)
309
310     return render(request, 'club/stats.html',
311                   {'days': days})
312