fix race in filters
[wolnelektury.git] / src / wolnelektury / views.py
1 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
3 #
4 from datetime import date, datetime
5 from urllib.parse import quote_plus
6 import feedparser
7 from allauth.socialaccount.views import SignupView
8
9 from django.conf import settings
10 from django.contrib import auth
11 from django.contrib.auth.decorators import login_required
12 from django.contrib.auth.forms import AuthenticationForm
13 from django.contrib.auth.views import LoginView
14 from django.core.cache import cache
15 from django.views.generic import FormView
16 from django.http import HttpResponse, HttpResponseRedirect
17 from django.shortcuts import render
18 from django.utils.translation import gettext_lazy as _
19 from django.views.decorators.cache import never_cache
20
21 from ajaxable.utils import AjaxableFormView
22 from ajaxable.utils import placeholdized
23 from catalogue.models import Book, Collection, Tag, Fragment
24 import club.models
25 from social.utils import get_or_choose_cite
26 from wolnelektury.forms import RegistrationForm, SocialSignupForm, WLAuthenticationForm
27
28
29 def main_page_2022(request):
30     ctx = {}
31     ctx['last_published'] = Book.objects.exclude(cover_clean='').filter(findable=True, parent=None).order_by('-created_at')[:10]
32     ctx['recommended_collection'] = Collection.objects.filter(listed=True, role='recommend').order_by('?').first()
33     ctx['ambassadors'] = club.models.Ambassador.objects.all().order_by('?')
34     ctx['widget'] = settings.WIDGETS.get(request.GET.get('w'))
35     if not ctx['widget'] and request.EXPERIMENTS['sowka'].value:
36         ctx['widget'] = settings.WIDGETS['pan-sowka']
37     return render(request, '2022/main_page.html', ctx)
38
39 @never_cache
40 def main_page(request):
41     if request.GET.get('w') in settings.WIDGETS:
42         request.EXPERIMENTS['layout'].override(True)
43     if request.EXPERIMENTS['sowka'].value:
44         request.EXPERIMENTS['layout'].override(True)
45
46     if request.EXPERIMENTS['layout'].value:
47         return main_page_2022(request)
48
49     ctx = {
50         'last_published': Book.objects.exclude(cover_clean='').filter(findable=True, parent=None).order_by('-created_at')[:6],
51         'theme_books': [],
52     }
53
54     # FIXME: find this theme and books properly.
55     if Fragment.objects.exists():
56         while True:
57             ctx['theme'] = Tag.objects.filter(category='theme').order_by('?')[:1][0]
58             tf = Fragment.tagged.with_any([ctx['theme']]).select_related('book').filter(book__findable=True).order_by('?')[:100]
59             if not tf:
60                 continue
61             ctx['theme_fragment'] = tf[0]
62             for f in tf:
63                 if f.book not in ctx['theme_books']:
64                     ctx['theme_books'].append(f.book)
65                 if len(ctx['theme_books']) == 3:
66                     break
67             break
68
69     # Choose collections for main.
70     ctx['collections'] = Collection.objects.filter(listed=True).order_by('?')[:4]
71
72     best = []
73     best_places = 5
74     recommended_collection = None
75     for recommended in Collection.objects.filter(listed=True, role='recommend').order_by('?'):
76         if recommended_collection is None:
77             recommended_collection = recommended
78         books = list(recommended.get_books().exclude(id__in=[b.id for b in best]).order_by('?')[:best_places])
79         best.extend(books)
80         best_places -= len(books)
81         if not best_places:
82             break
83     ctx['recommended_collection'] = recommended_collection
84     if best_places:
85         best.extend(
86             list(
87                 Book.objects.filter(findable=True).exclude(id__in=[b.id for b in best]).order_by('?')[:best_places]
88             )
89         )
90     ctx['best'] = best
91
92     return render(request, "main_page.html", ctx)
93
94
95 class WLLoginView(LoginView):
96     form_class = WLAuthenticationForm
97
98
99 wl_login_view = WLLoginView.as_view()
100
101
102 class LoginFormView(AjaxableFormView):
103     form_class = AuthenticationForm
104     template = "auth/login.html"
105     placeholdize = True
106     title = _('Sign in')
107     submit = _('Sign in')
108     ajax_redirect = True
109
110     def __call__(self, request):
111         if request.EXPERIMENTS['layout'].value:
112             return wl_login_view(request)
113
114         if request.user.is_authenticated:
115             return self.redirect_or_refresh(
116                 request, '/',
117                 message=_('Already logged in as user %(user)s', ) % {'user': request.user.username})
118         return super(LoginFormView, self).__call__(request)
119
120     def success(self, form, request):
121         auth.login(request, form.get_user())
122
123
124 class WLRegisterView(FormView):
125     form_class = RegistrationForm
126     template_name = 'registration/register.html'
127
128     def form_valid(self, form):
129         form.save()
130         user = auth.authenticate(
131             username=form.cleaned_data['username'],
132             password=form.cleaned_data['password1']
133         )
134         auth.login(self.request, user)
135         return HttpResponseRedirect(quote_plus(self.request.GET.get('next', '/'), safe='/?='))
136
137 wl_register_view = WLRegisterView.as_view()
138
139
140 class RegisterFormView(AjaxableFormView):
141     form_class = RegistrationForm
142     template = "auth/register.html"
143     placeholdize = True
144     title = _('Register')
145     submit = _('Register')
146     ajax_redirect = True
147     form_prefix = 'register'
148     honeypot = True
149
150     def __call__(self, request):
151         if request.EXPERIMENTS['layout'].value:
152             return wl_register_view(request)
153
154         if request.user.is_authenticated:
155             return self.redirect_or_refresh(
156                 request, '/',
157                 message=_('Already logged in as user %(user)s', ) % {'user': request.user.username})
158         return super(RegisterFormView, self).__call__(request)
159
160     def success(self, form, request):
161         form.save()
162         user = auth.authenticate(
163             username=form.cleaned_data['username'],
164             password=form.cleaned_data['password1']
165         )
166         auth.login(request, user)
167
168
169 class LoginRegisterFormView(LoginFormView):
170     template = 'auth/login_register.html'
171     title = _('You have to be logged in to continue')
172
173     def extra_context(self, request, obj):
174         return {
175             "register_form": placeholdized(RegistrationForm(prefix='register')),
176             "register_submit": _('Register'),
177         }
178
179
180 @never_cache
181 def logout_then_redirect(request):
182     auth.logout(request)
183     return HttpResponseRedirect(quote_plus(request.GET.get('next', '/'), safe='/?='))
184
185
186 @never_cache
187 def clock(request):
188     """ Provides server UTC time for jquery.countdown,
189     in a format suitable for Date.parse()
190     """
191     return HttpResponse(datetime.utcnow().strftime('%Y/%m/%d %H:%M:%S UTC'))
192
193
194 def publish_plan(request):
195     cache_key = "publish_plan"
196     plan = cache.get(cache_key)
197
198     if plan is None:
199         plan = []
200         try:
201             feed = feedparser.parse(settings.PUBLISH_PLAN_FEED)
202         except:
203             pass
204         else:
205             for i in range(len(feed['entries'])):
206                 plan.append({
207                     'title': feed['entries'][i].title,
208                     'link': feed['entries'][i].link,
209                     })
210         cache.set(cache_key, plan, 1800)
211
212     return render(request, "publish_plan.html", {'plan': plan})
213
214
215 @login_required
216 def user_settings(request):
217     return render(request, "user.html")
218
219
220 def widget(request):
221     return render(request, 'widget.html')
222
223
224 class SocialSignupView(SignupView):
225     form_class = SocialSignupForm
226
227
228 def exception_test(request):
229     msg = request.GET.get('msg')
230     if msg:
231         raise Exception('Exception test: %s' % msg)
232     else:
233         raise Exception('Exception test')
234
235
236 def post_test(request):
237     return render(request, 'post_test.html', {'action': '/api/reading/jego-zasady/complete/'})