Skip to content

Commit

Permalink
Add options for personal schedule and favourite sessions for ticket h…
Browse files Browse the repository at this point in the history
…olders (#147)

* Add options for personal schedule and favourite sessions for ticket holder
* update fav count css and popup warning if user not logged in yet to add favorite session
  • Loading branch information
odkhang authored Jul 30, 2024
1 parent a3c018a commit 663449e
Show file tree
Hide file tree
Showing 9 changed files with 382 additions and 42 deletions.
1 change: 1 addition & 0 deletions src/pretalx/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@
path("me", user.MeView.as_view(), name="user.me"),
path("auth/", obtain_auth_token),
path("events/<event>/", include(event_router.urls)),
path("events/<event>/favourite-talk/", submission.SubmissionFavouriteView.as_view()),
]
138 changes: 135 additions & 3 deletions src/pretalx/api/views/submission.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
from django.http import Http404
import json
import jwt
import logging
import datetime as dt
import requests

from django.db import IntegrityError
from django.http import Http404, JsonResponse
from django.shortcuts import get_object_or_404
from django.utils.decorators import method_decorator
from django.utils.functional import cached_property
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from django_filters import rest_framework as filters
from django_scopes import scopes_disabled
from rest_framework import viewsets
from rest_framework import viewsets, status
from rest_framework.views import APIView
from rest_framework.authentication import get_authorization_header

from pretalx.api.serializers.submission import (
ScheduleListSerializer,
Expand All @@ -13,17 +26,21 @@
TagSerializer,
)
from pretalx.schedule.models import Schedule
from pretalx.person.models import User
from pretalx.submission.models import Submission, SubmissionStates, Tag
from pretalx.submission.models.submission import SubmissionFavourite, \
SubmissionFavouriteSerializer

with scopes_disabled():

class SubmissionFilter(filters.FilterSet):
state = filters.MultipleChoiceFilter(choices=SubmissionStates.get_choices())

class Meta:
model = Submission
fields = ("state", "content_locale", "submission_type")

logger = logging.getLogger(__name__)


class SubmissionViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = SubmissionSerializer
Expand Down Expand Up @@ -128,3 +145,118 @@ def get_queryset(self):
if self.request.user.has_perm("orga.view_submissions", self.request.event):
return self.request.event.tags.all()
return Tag.objects.none()


class SubmissionFavouriteView(View):
"""
A view for handling user's favourite talks.
- GET: Retrieve the list of favourite talks for the authenticated user.
- PUT: Add talks to the user's favourite list.
"""

@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)

def get(self, request, *args, **kwargs) -> JsonResponse:
"""
Handle GET requests to retrieve the list of favourite talks
"""
try:
user_id = request.user.id
# user_id = 52
fav_talks = get_object_or_404(SubmissionFavourite, user=user_id)
return JsonResponse(fav_talks.talk_list, safe=False)
except Http404 as e:
# As user not have any favourite talk yet
return JsonResponse([], safe=False)
except Exception as e:
logger.error(f"unexpected error happened: {str(e)}")
return JsonResponse({'error': str(e)}, safe=False,
status=status.HTTP_503_SERVICE_UNAVAILABLE)

@staticmethod
def post(request, *args, **kwargs) -> JsonResponse:
"""
Handle POST requests to add talks to the user's favourite list.
"""
try:
user_id = request.user.id
if user_id is None:
user_id = SubmissionFavouriteView.get_user_from_token(request, request.event.venueless_settings)
talk_list = json.loads(request.body.decode())
talk_list_valid = []
for talk in talk_list:
with scopes_disabled():
if Submission.objects.filter(code=talk).exists():
talk_list_valid.append(talk)

data = {
"user": user_id,
"talk_list": talk_list_valid
}
serializer = SubmissionFavouriteSerializer(data=data)
if serializer.is_valid():
fav_talks = serializer.save(user_id, talk_list_valid)
# call to video for update favourite talks
token = SubmissionFavouriteView.get_user_video_token(request.user.code,
request.event.venueless_settings)
video_url = request.event.venueless_settings.url + "favourite-talk/"
header = {
"Content-Type": "application/json",
"Authorization": f"Bearer {token}"
}
requests.post(video_url, data=json.dumps(talk_list_valid), headers=header)
else:
logger.error(f"Validation error: {serializer.errors}")
return JsonResponse({'error': serializer.errors}, safe=False,
status=status.HTTP_400_BAD_REQUEST)

return JsonResponse(fav_talks.talk_list, safe=False,
status=status.HTTP_200_OK)

except Http404:
logger.info("User not login yet, so can't add favourite talks.")
return JsonResponse('user_not_logged_in', safe=False, status=status.HTTP_400_BAD_REQUEST)
except IntegrityError as e:
logger.error(f"Integrity error: {str(e)}")
return JsonResponse({'error': str(e)}, safe=False,
status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
logger.error(f"Unexpected error: {str(e)}")
return JsonResponse({'error': str(e)}, safe=False,
status=status.HTTP_503_SERVICE_UNAVAILABLE)

@staticmethod
def get_user_video_token(user_code, video_settings):
iat = dt.datetime.utcnow()
exp = iat + dt.timedelta(days=30)
payload = {
"iss": video_settings.issuer,
"aud": video_settings.audience,
"exp": exp,
"iat": iat,
"uid": user_code,
}
token = jwt.encode(payload, video_settings.secret, algorithm="HS256")
return token

@staticmethod
def get_user_from_token(request, video_settings):
auth_header = get_authorization_header(request).split()
if not auth_header:
raise Http404
if auth_header and auth_header[0].lower() == b'bearer':
if len(auth_header) == 1:
raise exceptions.AuthenticationFailed('Invalid token header. No credentials provided.')
elif len(auth_header) > 2:
raise exceptions.AuthenticationFailed(
'Invalid token header. Token string should not contain spaces.')
token_decode = jwt.decode(auth_header[1], video_settings.secret,
algorithms=["HS256"],
audience=video_settings.audience,
issuer=video_settings.issuer,
)
user_code = token_decode.get("uid")
return get_object_or_404(User, code=user_code).id
12 changes: 12 additions & 0 deletions src/pretalx/schedule/models/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from django.conf import settings
from django.db import models, transaction
from django.db.models import TextField
from django.db.models.functions import Cast
from django.db.utils import DatabaseError
from django.utils.functional import cached_property
from django.utils.timezone import now
Expand All @@ -18,6 +20,7 @@
from pretalx.schedule.notifications import render_notifications
from pretalx.schedule.signals import schedule_release
from pretalx.submission.models import SubmissionStates
from pretalx.submission.models.submission import SubmissionFavourite


class Schedule(PretalxModel):
Expand Down Expand Up @@ -664,6 +667,7 @@ def build_data(self, all_talks=False, filter_updated=None, all_rooms=False):
"duration": talk.submission.get_duration(),
"updated": talk.updated.isoformat(),
"state": talk.submission.state if all_talks else None,
"fav_count": count_fav_talk(talk.submission.code) if talk.submission else 0,
"do_not_record": talk.submission.do_not_record
}
)
Expand Down Expand Up @@ -710,3 +714,11 @@ def build_data(self, all_talks=False, filter_updated=None, all_rooms=False):
def __str__(self) -> str:
"""Help when debugging."""
return f"Schedule(event={self.event.slug}, version={self.version})"


def count_fav_talk(submission_code):
# Cast talk_list to TextField for using the contains lookup
count = SubmissionFavourite.objects.annotate(
talk_list_str=Cast('talk_list', TextField())
).filter(talk_list_str__contains=str(submission_code)).count()
return count
Loading

0 comments on commit 663449e

Please sign in to comment.