Add daily visits stats.
[wolnelektury.git] / src / api / 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 django.contrib.auth.decorators import login_required
5 from django import forms
6 from django.http import HttpResponse
7 from django.http import Http404
8 from django.shortcuts import render
9 from django.views.generic.base import View
10 from oauthlib.common import urlencode
11 from oauthlib.oauth1 import RequestTokenEndpoint, AccessTokenEndpoint
12 from oauthlib.oauth1 import AuthorizationEndpoint, OAuth1Error
13 from rest_framework.permissions import IsAuthenticated
14 from rest_framework.response import Response
15 from rest_framework.views import APIView
16 from rest_framework.generics import RetrieveAPIView, get_object_or_404
17 from catalogue.models import Book
18 from .models import BookUserData, KEY_SIZE, SECRET_SIZE
19 from . import serializers
20 from .request_validator import PistonRequestValidator
21 from .utils import oauthlib_request, oauthlib_response, vary_on_auth
22
23
24 class OAuth1RequestTokenEndpoint(RequestTokenEndpoint):
25     def _create_request(self, *args, **kwargs):
26         r = super(OAuth1RequestTokenEndpoint, self)._create_request(*args, **kwargs)
27         r.redirect_uri = 'oob'
28         return r
29
30     def create_request_token(self, request, credentials):
31         token = {
32             'oauth_token': self.token_generator()[:KEY_SIZE],
33             'oauth_token_secret': self.token_generator()[:SECRET_SIZE],
34         }
35         token.update(credentials)
36         self.request_validator.save_request_token(token, request)
37         return urlencode(token.items())
38
39
40 # Never Cache
41 class OAuth1RequestTokenView(View):
42     def __init__(self):
43         self.endpoint = OAuth1RequestTokenEndpoint(PistonRequestValidator())
44
45     def dispatch(self, request):
46         return oauthlib_response(
47             self.endpoint.create_request_token_response(
48                 **oauthlib_request(request)
49             )
50         )
51
52
53 class OAuthAuthenticationForm(forms.Form):
54     oauth_token = forms.CharField(widget=forms.HiddenInput)
55     oauth_callback = forms.CharField(widget=forms.HiddenInput)  # changed from URLField - too strict
56     # removed authorize_access - redundant
57
58
59 class OAuth1AuthorizationEndpoint(AuthorizationEndpoint):
60     def create_verifier(self, request, credentials):
61         verifier = super(OAuth1AuthorizationEndpoint, self).create_verifier(request, credentials)
62         return {
63             'oauth_token': verifier['oauth_token'],
64         }
65
66
67 @login_required
68 def oauth_user_auth(request):
69     endpoint = OAuth1AuthorizationEndpoint(PistonRequestValidator())
70
71     if request.method == "GET":
72         # Why not just get oauth_token here?
73         # This is fairly straightforward, in't?
74         try:
75             realms, credentials = endpoint.get_realms_and_credentials(
76                 **oauthlib_request(request))
77         except OAuth1Error as e:
78             return HttpResponse(e.message, status=400)
79         callback = request.GET.get('oauth_callback')
80
81         form = OAuthAuthenticationForm(initial={
82             'oauth_token': credentials['resource_owner_key'],
83             'oauth_callback': callback,
84         })
85
86         return render(request, 'oauth/authorize_token.html', {'form': form})
87
88     if request.method == "POST":
89         try:
90             response = oauthlib_response(
91                 endpoint.create_authorization_response(
92                     credentials={"user": request.user},
93                     **oauthlib_request(request)
94                 )
95             )
96         except OAuth1Error as e:
97             return HttpResponse(e.message, status=400)
98         else:
99             return response
100
101
102 class OAuth1AccessTokenEndpoint(AccessTokenEndpoint):
103     def _create_request(self, *args, **kwargs):
104         r = super(OAuth1AccessTokenEndpoint, self)._create_request(*args, **kwargs)
105         r.verifier = 'x' * 20
106         return r
107
108     def create_access_token(self, request, credentials):
109         request.realms = self.request_validator.get_realms(
110             request.resource_owner_key, request)
111         token = {
112             'oauth_token': self.token_generator()[:KEY_SIZE],
113             'oauth_token_secret': self.token_generator()[:SECRET_SIZE],
114             'oauth_authorized_realms': ' '.join(request.realms)
115         }
116         token.update(credentials)
117         self.request_validator.save_access_token(token, request)
118         return urlencode(token.items())
119
120
121 # Never cache
122 class OAuth1AccessTokenView(View):
123     def __init__(self):
124         self.endpoint = OAuth1AccessTokenEndpoint(PistonRequestValidator())
125
126     def dispatch(self, request):
127         return oauthlib_response(
128             self.endpoint.create_access_token_response(
129                 **oauthlib_request(request)
130             )
131         )
132
133
134 @vary_on_auth
135 class UserView(RetrieveAPIView):
136     permission_classes = [IsAuthenticated]
137     serializer_class = serializers.UserSerializer
138
139     def get_object(self):
140         return self.request.user
141
142
143 @vary_on_auth
144 class BookUserDataView(RetrieveAPIView):
145     permission_classes = [IsAuthenticated]
146     serializer_class = serializers.BookUserDataSerializer
147     lookup_field = 'book__slug'
148     lookup_url_kwarg = 'slug'
149
150     def get_queryset(self):
151         return BookUserData.objects.filter(user=self.request.user)
152
153     def get(self, *args, **kwargs):
154         try:
155             return super(BookUserDataView, self).get(*args, **kwargs)
156         except Http404:
157             return Response({"state": "not_started"})
158
159     def post(self, request, slug, state):
160         if state not in ('reading', 'complete'):
161             raise Http404
162
163         book = get_object_or_404(Book, slug=slug)
164         instance = BookUserData.update(book, request.user, state)
165         serializer = self.get_serializer(instance)
166         return Response(serializer.data)
167
168
169 class BlogView(APIView):
170     def get(self, request):
171         return Response([])