diff --git a/.vscode/settings.json b/.vscode/settings.json index db15d0754..8bb1a44d9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -31,7 +31,7 @@ "editor.defaultFormatter": "charliermarsh.ruff", "editor.codeActionsOnSave": { "source.organizeImports.isort": "never", - "source.organizeImports": "explicit", + "source.organizeImports.ruff": "explicit", } }, "git.branchProtection": [ diff --git a/README.md b/README.md index 14f328919..a0fc71c67 100644 --- a/README.md +++ b/README.md @@ -92,10 +92,9 @@ In the future we should register this method in the `apps.py` file of the app, b #### Versions -We currently have 2 versions of the API. -The first version is the `v1` API, which is the old API. +We used to have 2 versions of the API however `v1` is no longer available. +The first version is the `v1` API, which is the old API and is no longer used. The second version is the `v2` API, which is the new API that is actively being developed. -The `v1` API is deprecated and will be removed in the future. #### Swagger documentation @@ -104,7 +103,7 @@ This documentation is available at `/api/docs/`. #### Authentication -`v1` uses token authentication. `v2` uses OAuth2 authentication, which is the new standard for authentication. +The API uses OAuth2 authentication, which is the new standard for authentication. The OAuth2 authentication is implemented using the `django-oauth-toolkit` package. #### Throttling @@ -113,7 +112,7 @@ The API has throttling enabled. #### Other (internal) APIs -Apart from the main versions (`v1` and `v2`), we also have a few specific mini-APIs that are used for specific purposes and are not really open to the public. +Apart from the main version (`v2`), we also have a few specific mini-APIs that are used for specific purposes and are not really open to the public. These are the `calendarjs` and `facedetection` APIs. The `calendarjs` API is only used by the calendar on the website (to query events) and the `facedetection` API is used by the face detection service to post face encodings. ## About concrexit diff --git a/website/activemembers/api/v1/__init__.py b/website/activemembers/api/v1/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/website/activemembers/api/v1/serializers.py b/website/activemembers/api/v1/serializers.py deleted file mode 100644 index 2b808eb7a..000000000 --- a/website/activemembers/api/v1/serializers.py +++ /dev/null @@ -1,57 +0,0 @@ -from rest_framework import serializers - -from activemembers.models import MemberGroup, MemberGroupMembership -from members.api.v1.serializers import MemberListSerializer -from thaliawebsite.api.v1.cleaned_model_serializer import CleanedModelSerializer - - -class MemberGroupSerializer(CleanedModelSerializer): - """MemberGroup serializer.""" - - class Meta: - """Meta class for the serializer.""" - - model = MemberGroup - fields = ( - "pk", - "name", - "type", - "since", - "until", - "contact_address", - "photo", - "chair", - "members", - ) - - members = serializers.SerializerMethodField("_members") - chair = serializers.SerializerMethodField("_chair") - type = serializers.SerializerMethodField("_type") - - def _members(self, instance): - memberships = MemberGroupMembership.active_objects.filter(group=instance) - members = [x.member for x in memberships.select_related("member")] - return MemberListSerializer(context=self.context, many=True).to_representation( - members - ) - - def _chair(self, instance): - membership = ( - MemberGroupMembership.active_objects.filter(chair=True, group=instance) - .select_related("member") - .first() - ) - if membership: - return MemberListSerializer(context=self.context).to_representation( - membership.member - ) - return None - - def _type(self, instance): - if hasattr(instance, "board"): - return "board" - if hasattr(instance, "committee"): - return "committee" - if hasattr(instance, "society"): - return "society" - return None diff --git a/website/activemembers/api/v1/urls.py b/website/activemembers/api/v1/urls.py deleted file mode 100644 index 41c7b5263..000000000 --- a/website/activemembers/api/v1/urls.py +++ /dev/null @@ -1,7 +0,0 @@ -from rest_framework import routers - -from activemembers.api.v1 import viewsets - -router = routers.SimpleRouter() -router.register(r"activemembers/groups", viewsets.MemberGroupViewset) -urlpatterns = router.urls diff --git a/website/activemembers/api/v1/viewsets.py b/website/activemembers/api/v1/viewsets.py deleted file mode 100644 index 0877507b8..000000000 --- a/website/activemembers/api/v1/viewsets.py +++ /dev/null @@ -1,22 +0,0 @@ -"""DRF viewsets defined by the members package.""" - -from oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope -from rest_framework import filters, viewsets - -from activemembers.api.v1.serializers import MemberGroupSerializer -from activemembers.models import MemberGroup - - -class MemberGroupViewset(viewsets.ReadOnlyModelViewSet): - """Viewset that renders or edits a member.""" - - required_scopes = ["activemembers:read"] - permission_classes = [IsAuthenticatedOrTokenHasScope] - queryset = MemberGroup.active_objects.all() - serializer_class = MemberGroupSerializer - filter_backends = ( - filters.OrderingFilter, - filters.SearchFilter, - ) - search_fields = ("name", "contact_email", "contact_mailinglist__name") - lookup_field = "pk" diff --git a/website/announcements/api/v1/__init__.py b/website/announcements/api/v1/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/website/announcements/api/v1/serializers.py b/website/announcements/api/v1/serializers.py deleted file mode 100644 index de6bf9b43..000000000 --- a/website/announcements/api/v1/serializers.py +++ /dev/null @@ -1,26 +0,0 @@ -from rest_framework import serializers - -from announcements.models import Slide -from thaliawebsite.api.v1.cleaned_model_serializer import CleanedModelSerializer -from utils.media.services import get_thumbnail_url - - -class SlideSerializer(CleanedModelSerializer): - """Slide serializer.""" - - class Meta: - """Meta class for the serializer.""" - - model = Slide - fields = ( - "pk", - "title", - "content", - "order", - "url", - ) - - content = serializers.SerializerMethodField("_file") - - def _file(self, obj): - return get_thumbnail_url(obj.content, "slide", absolute_url=True) diff --git a/website/announcements/api/v1/urls.py b/website/announcements/api/v1/urls.py deleted file mode 100644 index 4f98e222a..000000000 --- a/website/announcements/api/v1/urls.py +++ /dev/null @@ -1,7 +0,0 @@ -from rest_framework import routers - -from . import viewsets - -router = routers.SimpleRouter() -router.register(r"announcements/slides", viewsets.SlideViewset) -urlpatterns = router.urls diff --git a/website/announcements/api/v1/viewsets.py b/website/announcements/api/v1/viewsets.py deleted file mode 100644 index 75394c08b..000000000 --- a/website/announcements/api/v1/viewsets.py +++ /dev/null @@ -1,21 +0,0 @@ -from rest_framework import viewsets - -from announcements.models import Slide - -from .serializers import SlideSerializer - - -class SlideViewset(viewsets.ReadOnlyModelViewSet): - """Viewset for slides.""" - - queryset = Slide.objects.all() - serializer_class = SlideSerializer - - def get_queryset(self): - queryset = Slide.objects.all() - if not self.request.member: - queryset = queryset.filter(members_only=False) - - ids = (slide.pk for slide in queryset if slide.is_visible) - - return Slide.objects.filter(pk__in=ids) diff --git a/website/events/api/v1/__init__.py b/website/events/api/v1/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/website/events/api/v1/serializers/__init__.py b/website/events/api/v1/serializers/__init__.py deleted file mode 100644 index b86cc4afb..000000000 --- a/website/events/api/v1/serializers/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -from .event_registrations import ( - EventRegistrationAdminListSerializer, - EventRegistrationListSerializer, - EventRegistrationSerializer, -) -from .events import EventListSerializer, EventRetrieveSerializer - -__all__ = [ - "EventRetrieveSerializer", - "EventListSerializer", - "EventRegistrationSerializer", - "EventRegistrationListSerializer", - "EventRegistrationAdminListSerializer", -] diff --git a/website/events/api/v1/serializers/event_registrations.py b/website/events/api/v1/serializers/event_registrations.py deleted file mode 100644 index 19236c710..000000000 --- a/website/events/api/v1/serializers/event_registrations.py +++ /dev/null @@ -1,217 +0,0 @@ -from django.conf import settings -from django.templatetags.static import static - -from rest_framework import serializers -from rest_framework.fields import empty - -from events import services -from events.exceptions import RegistrationError -from events.models import EventRegistration, RegistrationInformationField -from payments.api.v1.fields import PaymentTypeField -from payments.models import Payment -from thaliawebsite.api.services import create_image_thumbnail_dict -from thaliawebsite.api.v1.cleaned_model_serializer import CleanedModelSerializer - - -class EventRegistrationListSerializer(CleanedModelSerializer): - """Custom registration list serializer.""" - - class Meta: - model = EventRegistration - fields = ("pk", "member", "name", "avatar") - - name = serializers.SerializerMethodField("_name") - avatar = serializers.SerializerMethodField("_avatar") - member = serializers.SerializerMethodField("_member") - - def _member(self, instance): - if instance.member: - return instance.member.pk - return None - - def _name(self, instance): - if instance.member: - return instance.member.profile.display_name() - return instance.name - - def _avatar(self, instance): - placeholder = self.context["request"].build_absolute_uri( - static("members/images/default-avatar.jpg") - ) - file = None - if instance.member and instance.member.profile.photo: - file = instance.member.profile.photo - return create_image_thumbnail_dict( - file, placeholder=placeholder, size_large="avatar_large" - ) - - -class EventRegistrationAdminListSerializer(EventRegistrationListSerializer): - """Custom registration admin list serializer.""" - - class Meta: - model = EventRegistration - fields = ( - "pk", - "member", - "name", - "registered_on", - "is_cancelled", - "is_late_cancellation", - "queue_position", - "payment", - "present", - "avatar", - ) - - registered_on = serializers.DateTimeField(source="date") - is_cancelled = serializers.SerializerMethodField("_is_cancelled") - is_late_cancellation = serializers.SerializerMethodField("_is_late_cancellation") - queue_position = serializers.SerializerMethodField("_queue_position") - payment = PaymentTypeField(source="payment.type", choices=Payment.PAYMENT_TYPE) - - def _is_late_cancellation(self, instance): - return instance.is_late_cancellation() - - def _queue_position(self, instance): - pos = instance.queue_position - return pos if pos and pos > 0 else None - - def _is_cancelled(self, instance): - return instance.date_cancelled is not None - - def _name(self, instance): - if instance.member: - return instance.member.get_full_name() - return instance.name - - -class EventRegistrationSerializer(serializers.ModelSerializer): - """Registration serializer.""" - - information_fields = None - - class Meta: - model = EventRegistration - fields = ( - "pk", - "member", - "name", - "photo", - "avatar", - "registered_on", - "is_late_cancellation", - "is_cancelled", - "queue_position", - "fields", - "payment", - "present", - ) - - name = serializers.SerializerMethodField("_name") - photo = serializers.SerializerMethodField("_photo") - avatar = serializers.SerializerMethodField("_avatar") - member = serializers.SerializerMethodField("_member") - payment = PaymentTypeField(source="payment.type", choices=Payment.PAYMENT_TYPE) - registered_on = serializers.DateTimeField(source="date", read_only=True) - is_cancelled = serializers.SerializerMethodField("_is_cancelled") - is_late_cancellation = serializers.SerializerMethodField("_is_late_cancellation") - fields = serializers.HiddenField(default="") - - def _is_late_cancellation(self, instance): - val = instance.is_late_cancellation() - return False if val is None else val - - def _is_cancelled(self, instance): - return instance.date_cancelled is not None - - def _member(self, instance): - if instance.member: - return instance.member.pk - return None - - def _name(self, instance): - if instance.member: - return instance.member.profile.display_name() - return instance.name - - def _photo(self, instance): - if instance.member and instance.member.profile.photo: - return self.context["request"].build_absolute_uri( - f"{settings.MEDIA_URL}{instance.member.profile.photo}" - ) - return self.context["request"].build_absolute_uri( - static("members/images/default-avatar.jpg") - ) - - def _avatar(self, instance): - placeholder = self.context["request"].build_absolute_uri( - static("members/images/default-avatar.jpg") - ) - file = None - if instance.member and instance.member.profile.photo: - file = instance.member.profile.photo - return create_image_thumbnail_dict( - file, placeholder=placeholder, size_large="avatar_large" - ) - - def __init__(self, instance=None, data=empty, **kwargs): - super().__init__(instance, data, **kwargs) - try: - if instance: - self.information_fields = services.registration_fields( - kwargs["context"]["request"], registration=instance - ) - except RegistrationError: - pass - - def get_fields(self): - fields = super().get_fields() - - if self.information_fields: - for key, field in self.information_fields.items(): - key = f"fields[{key}]" - field_type = field["type"] - - if field_type == RegistrationInformationField.BOOLEAN_FIELD: - fields[key] = serializers.BooleanField( - required=False, write_only=True - ) - elif field_type == RegistrationInformationField.INTEGER_FIELD: - fields[key] = serializers.IntegerField( - required=field["required"], - write_only=True, - allow_null=not field["required"], - ) - elif field_type == RegistrationInformationField.TEXT_FIELD: - fields[key] = serializers.CharField( - required=field["required"], - write_only=True, - allow_blank=not field["required"], - allow_null=not field["required"], - ) - - fields[key].label = field["label"] - fields[key].help_text = field["description"] - fields[key].initial = field["value"] - fields[key].default = field["value"] - - try: - if key in self.information_fields: - fields[key].initial = self.validated_data[key] - except AssertionError: - pass - - return fields - - def to_representation(self, instance): - data = super().to_representation(instance) - data["fields"] = self.information_fields - return data - - def field_values(self): - return ( - (name[7 : len(name) - 1], value) - for name, value in self.validated_data.items() - if "info_field" in name - ) diff --git a/website/events/api/v1/serializers/events.py b/website/events/api/v1/serializers/events.py deleted file mode 100644 index 62dbced38..000000000 --- a/website/events/api/v1/serializers/events.py +++ /dev/null @@ -1,156 +0,0 @@ -from html import unescape - -from django.utils.html import strip_spaces_between_tags, strip_tags - -from rest_framework import serializers - -from events import services -from events.models import Event, EventRegistration -from thaliawebsite.api.v1.cleaned_model_serializer import CleanedModelSerializer -from thaliawebsite.templatetags.bleach_tags import bleach -from utils.snippets import create_google_maps_url - -from .event_registrations import EventRegistrationAdminListSerializer - - -class EventRetrieveSerializer(CleanedModelSerializer): - """Serializer for events.""" - - class Meta: - model = Event - fields = ( - "pk", - "title", - "description", - "caption", - "start", - "end", - "organisers", - "category", - "registration_start", - "registration_end", - "cancel_deadline", - "location", - "map_location", - "price", - "fine", - "max_participants", - "num_participants", - "user_registration", - "registration_allowed", - "no_registration_message", - "has_fields", - "is_pizza_event", - "google_maps_url", - "is_admin", - "cancel_too_late_message", - ) - - description = serializers.SerializerMethodField("_description") - user_registration = serializers.SerializerMethodField("_user_registration") - num_participants = serializers.SerializerMethodField("_num_participants") - registration_allowed = serializers.SerializerMethodField("_registration_allowed") - has_fields = serializers.SerializerMethodField("_has_fields") - is_pizza_event = serializers.SerializerMethodField("_is_pizza_event") - google_maps_url = serializers.SerializerMethodField("_google_maps_url") - is_admin = serializers.SerializerMethodField("_is_admin") - - def _description(self, instance): - return strip_spaces_between_tags(bleach(instance.description)) - - def _num_participants(self, instance): - participant_count = instance.participants.count() - if instance.max_participants and participant_count > instance.max_participants: - return instance.max_participants - return participant_count - - def _user_registration(self, instance): - try: - if self.context["request"].member: - reg = instance.eventregistration_set.get( - member=self.context["request"].member - ) - return EventRegistrationAdminListSerializer( - reg, context=self.context - ).data - except EventRegistration.DoesNotExist: - pass - return None - - def _registration_allowed(self, instance): - member = self.context["request"].member - return ( - self.context["request"].user.is_authenticated - and member.has_active_membership - and member.can_attend_events - ) - - def _has_fields(self, instance): - return instance.has_fields - - def _is_pizza_event(self, instance): - return instance.has_food_event - - def _google_maps_url(self, instance): - return self.context["request"].build_absolute_uri( - create_google_maps_url(instance.map_location, zoom=13, size="450x250") - ) - - def _is_admin(self, instance): - member = self.context["request"].member - return services.is_organiser(member, instance) - - -class EventListSerializer(CleanedModelSerializer): - """Custom list serializer for events.""" - - class Meta: - model = Event - fields = ( - "pk", - "title", - "description", - "start", - "end", - "location", - "price", - "registered", - "present", - "pizza", - "registration_allowed", - ) - - description = serializers.SerializerMethodField("_description") - registered = serializers.SerializerMethodField("_registered") - pizza = serializers.SerializerMethodField("_pizza") - present = serializers.SerializerMethodField("_present") - - def _description(self, instance): - return unescape(strip_tags(instance.description)) - - def _registered(self, instance): - try: - registered = services.is_user_registered( - self.context["request"].user, - instance, - ) - if registered is None: - return False - return registered - except AttributeError: - return False - - def _pizza(self, instance): - return instance.has_food_event - - def _present(self, instance): - try: - present = services.is_user_present( - self.context["request"].user, - instance, - ) - if present is None: - return False - return present - except AttributeError: - return False diff --git a/website/events/api/v1/urls.py b/website/events/api/v1/urls.py deleted file mode 100644 index 88a509de1..000000000 --- a/website/events/api/v1/urls.py +++ /dev/null @@ -1,8 +0,0 @@ -from rest_framework import routers - -from events.api.v1 import viewsets - -router = routers.SimpleRouter() -router.register(r"events", viewsets.EventViewset) -router.register(r"registrations", viewsets.EventRegistrationViewSet) -urlpatterns = router.urls diff --git a/website/events/api/v1/viewsets/__init__.py b/website/events/api/v1/viewsets/__init__.py deleted file mode 100644 index 72c98e48f..000000000 --- a/website/events/api/v1/viewsets/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from .event_registrations import EventRegistrationViewSet -from .events import EventViewset - -__all__ = [ - "EventRegistrationViewSet", - "EventViewset", -] diff --git a/website/events/api/v1/viewsets/event_registrations.py b/website/events/api/v1/viewsets/event_registrations.py deleted file mode 100644 index d97e15d43..000000000 --- a/website/events/api/v1/viewsets/event_registrations.py +++ /dev/null @@ -1,71 +0,0 @@ -from rest_framework.exceptions import NotFound, PermissionDenied -from rest_framework.mixins import RetrieveModelMixin, UpdateModelMixin -from rest_framework.permissions import IsAuthenticated -from rest_framework.response import Response -from rest_framework.viewsets import GenericViewSet - -from events import services -from events.api.v1.serializers import EventRegistrationSerializer -from events.exceptions import RegistrationError -from events.models import EventRegistration -from payments.exceptions import PaymentError - - -class EventRegistrationViewSet(RetrieveModelMixin, UpdateModelMixin, GenericViewSet): - """Defines the viewset for registrations, requires an authenticated user. - - Has custom update and destroy methods that use the services. - """ - - queryset = EventRegistration.objects.all() - serializer_class = EventRegistrationSerializer - permission_classes = [IsAuthenticated] - - def get_serializer_context(self): - context = super().get_serializer_context() - context["request"] = self.request - return context - - def get_object(self): - instance = super().get_object() - if ( - instance.name or instance.member.pk != self.request.member.pk - ) and not services.is_organiser(self.request.member, instance.event): - raise NotFound() - - return instance - - def perform_update(self, serializer): - try: - registration = serializer.instance - - member = self.request.member - if ( - member - and member.has_perm("events.change_eventregistration") - and services.is_organiser(member, registration.event) - ): - services.update_registration_by_organiser( - registration, self.request.member, serializer.validated_data - ) - - services.update_registration( - registration=registration, - field_values=serializer.field_values(), - actor=self.request.member, - ) - serializer.information_fields = services.registration_fields( - serializer.context["request"], registration=registration - ) - except RegistrationError as e: - raise PermissionDenied(detail=e) from e - except PaymentError as e: - raise PermissionDenied(detail=e) from e - - def destroy(self, request, pk=None, **kwargs): - registration = self.get_object() - try: - services.cancel_registration(registration.member, registration.event) - return Response(status=204) - except RegistrationError as e: - raise PermissionDenied(detail=e) from e diff --git a/website/events/api/v1/viewsets/events.py b/website/events/api/v1/viewsets/events.py deleted file mode 100644 index 2bef42cee..000000000 --- a/website/events/api/v1/viewsets/events.py +++ /dev/null @@ -1,113 +0,0 @@ -from django.utils import timezone - -from rest_framework import filters, viewsets -from rest_framework.decorators import action -from rest_framework.exceptions import PermissionDenied -from rest_framework.generics import get_object_or_404 -from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly -from rest_framework.response import Response -from rest_framework.settings import api_settings - -from events import services -from events.api.v1.serializers import ( - EventListSerializer, - EventRegistrationAdminListSerializer, - EventRegistrationListSerializer, - EventRegistrationSerializer, - EventRetrieveSerializer, -) -from events.exceptions import RegistrationError -from events.models import Event, EventRegistration -from utils.snippets import extract_date_range - - -class EventViewset(viewsets.ReadOnlyModelViewSet): - """Define the viewset for events, requires an authenticated user and enables ordering on the event start/end.""" - - queryset = Event.objects.filter(published=True) - permission_classes = [IsAuthenticatedOrReadOnly] - filter_backends = ( - filters.OrderingFilter, - filters.SearchFilter, - ) - ordering_fields = ("start", "end") - search_fields = ("title",) - - def get_queryset(self): - queryset = Event.objects.filter(published=True) - - if ( - self.action == "retrieve" - or api_settings.SEARCH_PARAM in self.request.query_params - ): - return queryset - - start, end = extract_date_range(self.request, allow_empty=True) - - if start is not None: - queryset = queryset.filter(start__gte=start) - if end is not None: - queryset = queryset.filter(end__lte=end) - if start is None and end is None: - queryset = queryset.filter(end__gte=timezone.now()) - - return queryset - - def get_serializer_class(self): - if self.action == "retrieve": - return EventRetrieveSerializer - return EventListSerializer - - @action(detail=True, methods=["get", "post"], permission_classes=(IsAuthenticated,)) - def registrations(self, request, pk): - """Define a custom route for the event's registrations, can filter on registration status if the user is an organiser. - - :param request: the request object - :param pk: the primary key of the event - :return: the registrations of the event - """ - event = get_object_or_404(Event, pk=pk) - - if request.method.lower() == "post": - try: - registration = services.create_registration(request.member, event) - serializer = EventRegistrationSerializer( - instance=registration, context={"request": request} - ) - return Response(status=201, data=serializer.data) - except RegistrationError as e: - raise PermissionDenied(detail=e) from e - - status = request.query_params.get("status", None) - - # Make sure you can only access other registrations when you have - # the permissions to do so - context = {"request": request} - if services.is_organiser(self.request.member, event): - queryset = EventRegistration.objects.filter(event=pk) - if status == "queued": - queryset = EventRegistration.objects.filter( - event=pk, date_cancelled=None - )[event.max_participants :] - elif status == "cancelled": - queryset = EventRegistration.objects.filter( - event=pk, date_cancelled__not=None - ) - elif status == "registered": - queryset = EventRegistration.objects.filter( - event=pk, date_cancelled=None - )[: event.max_participants] - - serializer = EventRegistrationAdminListSerializer( - queryset, many=True, context=context - ) - else: - serializer = EventRegistrationListSerializer( - EventRegistration.objects.filter(event=pk, date_cancelled=None)[ - : event.max_participants - ], - many=True, - context=context, - ) - - return Response(serializer.data) diff --git a/website/events/services.py b/website/events/services.py index b74e4785b..63d12779d 100644 --- a/website/events/services.py +++ b/website/events/services.py @@ -15,8 +15,6 @@ categories, status, ) -from payments.api.v1.fields import PaymentTypeField -from payments.services import create_payment, delete_payment from utils.snippets import datetime_to_lectureyear @@ -169,15 +167,6 @@ def user_registration_pending(member, event): return False -def is_user_present(member, event): - if not event.registration_required or not member.is_authenticated: - return None - - return event.registrations.filter( - member=member, date_cancelled=None, present=True - ).exists() - - def event_permissions(member, event: Event, name=None, registration_prefetch=False): """Return a dictionary with the available event permissions of the user. @@ -477,29 +466,6 @@ def registration_fields(request, member=None, event=None, registration=None, nam raise RegistrationError(_("You are not allowed to update this registration.")) -def update_registration_by_organiser(registration, member, data): - if not is_organiser(member, registration.event): - raise RegistrationError(_("You are not allowed to update this registration.")) - - if "present" in data: - registration.present = data["present"] - - registration.save() - - if "payment" in data: - if data["payment"]["type"] == PaymentTypeField.NO_PAYMENT: - if registration.payment is not None: - delete_payment(registration, member) - else: - create_payment( - model_payable=registration, - processed_by=member, - pay_type=data["payment"]["type"], - ) - - registration.refresh_from_db(fields=["payment"]) - - def generate_category_statistics() -> dict: """Generate statistics about events per category.""" current_year = datetime_to_lectureyear(timezone.now()) diff --git a/website/events/tests/test_api.py b/website/events/tests/test_api.py index 88cd3384f..9063edaf9 100644 --- a/website/events/tests/test_api.py +++ b/website/events/tests/test_api.py @@ -1,22 +1,13 @@ import datetime +from activemembers.models import Committee from django.test import TestCase, override_settings from django.urls import reverse from django.utils import timezone - -from rest_framework.test import APIClient - -from activemembers.models import Committee -from events.models import ( - BooleanRegistrationInformation, - Event, - EventRegistration, - IntegerRegistrationInformation, - RegistrationInformationField, - TextRegistrationInformation, -) +from events.models import Event, EventRegistration from events.models.external_event import ExternalEvent from members.models import Member +from rest_framework.test import APIClient @override_settings(SUSPEND_SIGNALS=True) @@ -54,317 +45,6 @@ def setUp(self): self.client = APIClient() self.client.force_login(self.member) - def test_registration_register_not_required(self): - response = self.client.post("/api/v1/events/1/registrations/", follow=True) - self.assertEqual(response.status_code, 201) - self.assertEqual(self.event.participants.count(), 1) - - def test_registration_register(self): - self.event.registration_start = timezone.now() - datetime.timedelta(hours=1) - self.event.registration_end = timezone.now() + datetime.timedelta(hours=1) - self.event.cancel_deadline = timezone.now() + datetime.timedelta(hours=1) - self.event.save() - response = self.client.post("/api/v1/events/1/registrations/", follow=True) - self.assertEqual(response.status_code, 201) - self.assertEqual(response.data["member"], self.member.pk) - self.assertEqual(self.event.participants.count(), 1) - self.assertEqual(self.event.eventregistration_set.first().member, self.member) - - def test_registration_register_twice(self): - self.event.registration_start = timezone.now() - datetime.timedelta(hours=1) - self.event.registration_end = timezone.now() + datetime.timedelta(hours=1) - self.event.cancel_deadline = timezone.now() + datetime.timedelta(hours=1) - self.event.save() - response = self.client.post("/api/v1/events/1/registrations/", follow=True) - self.assertEqual(response.status_code, 201) - self.assertEqual(response.data["member"], self.member.pk) - response = self.client.post("/api/v1/events/1/registrations/", follow=True) - self.assertEqual(response.status_code, 403) - self.assertEqual(self.event.participants.count(), 1) - - def test_registration_register_closed(self): - self.event.registration_start = timezone.now() - datetime.timedelta(hours=2) - self.event.registration_end = timezone.now() - datetime.timedelta(hours=1) - self.event.cancel_deadline = timezone.now() + datetime.timedelta(hours=1) - self.event.save() - response = self.client.post("/api/v1/events/1/registrations/", follow=True) - self.assertEqual(response.status_code, 403) - self.assertEqual(self.event.participants.count(), 0) - - def test_registration_cancel(self): - self.event.registration_start = timezone.now() - datetime.timedelta(hours=1) - self.event.registration_end = timezone.now() + datetime.timedelta(hours=1) - self.event.cancel_deadline = timezone.now() + datetime.timedelta(hours=1) - self.event.save() - reg = EventRegistration.objects.create(event=self.event, member=self.member) - response = self.client.delete(f"/api/v1/registrations/{reg.pk}/", follow=True) - self.assertEqual(response.status_code, 204) - self.assertEqual(self.event.participants.count(), 0) - - def test_registration_register_no_fields(self): - self.event.registration_start = timezone.now() - datetime.timedelta(hours=1) - self.event.registration_end = timezone.now() + datetime.timedelta(hours=1) - self.event.cancel_deadline = timezone.now() + datetime.timedelta(hours=1) - self.event.save() - - field1 = RegistrationInformationField.objects.create( - pk=1, - event=self.event, - type=RegistrationInformationField.BOOLEAN_FIELD, - name="test bool", - required=False, - ) - - field2 = RegistrationInformationField.objects.create( - pk=2, - event=self.event, - type=RegistrationInformationField.INTEGER_FIELD, - name="test int", - required=False, - ) - - field3 = RegistrationInformationField.objects.create( - pk=3, - event=self.event, - type=RegistrationInformationField.TEXT_FIELD, - name="test text", - required=False, - ) - - response = self.client.post( - "/api/v1/events/1/registrations/", - {"info_field_1": True, "info_field_2": 42, "info_field_3": "text"}, - follow=True, - ) - self.assertEqual(response.status_code, 201) - self.assertEqual(response.data["member"], self.member.pk) - - self.assertEqual(self.event.participants.count(), 1) - registration = self.event.eventregistration_set.first() - self.assertEqual(field1.get_value_for(registration), None) - self.assertEqual(field2.get_value_for(registration), None) - self.assertEqual(field3.get_value_for(registration), None) - - def test_registration_missing_fields(self): - self.event.registration_start = timezone.now() - datetime.timedelta(hours=1) - self.event.registration_end = timezone.now() + datetime.timedelta(hours=1) - self.event.cancel_deadline = timezone.now() + datetime.timedelta(hours=1) - self.event.save() - - RegistrationInformationField.objects.create( - pk=1, - event=self.event, - type=RegistrationInformationField.BOOLEAN_FIELD, - name="test bool", - required=False, - ) - - RegistrationInformationField.objects.create( - pk=2, - event=self.event, - type=RegistrationInformationField.INTEGER_FIELD, - name="test int", - required=False, - ) - - RegistrationInformationField.objects.create( - pk=3, - event=self.event, - type=RegistrationInformationField.TEXT_FIELD, - name="test text", - required=False, - ) - - response = self.client.post("/api/v1/events/1/registrations/", follow=True) - self.assertEqual(response.status_code, 201) - self.assertEqual(response.data["member"], self.member.pk) - self.assertEqual(self.event.participants.count(), 1) - - def test_registration_register_fields_required(self): - self.event.registration_start = timezone.now() - datetime.timedelta(hours=1) - self.event.registration_end = timezone.now() + datetime.timedelta(hours=1) - self.event.cancel_deadline = timezone.now() + datetime.timedelta(hours=1) - self.event.save() - - RegistrationInformationField.objects.create( - event=self.event, - type=RegistrationInformationField.TEXT_FIELD, - name="test", - required=True, - ) - - response = self.client.post("/api/v1/events/1/registrations/", follow=True) - self.assertEqual(response.status_code, 201) - self.assertEqual(response.data["member"], self.member.pk) - self.assertEqual(self.event.participants.count(), 1) - - def test_registration_update_form_load_not_changes_fields(self): - self.event.registration_start = timezone.now() - datetime.timedelta(hours=1) - self.event.registration_end = timezone.now() + datetime.timedelta(hours=1) - self.event.cancel_deadline = timezone.now() + datetime.timedelta(hours=1) - self.event.save() - - field1 = RegistrationInformationField.objects.create( - pk=1, - event=self.event, - type=RegistrationInformationField.BOOLEAN_FIELD, - name="test bool", - required=False, - ) - - field2 = RegistrationInformationField.objects.create( - pk=2, - event=self.event, - type=RegistrationInformationField.INTEGER_FIELD, - name="test int", - required=False, - ) - - field3 = RegistrationInformationField.objects.create( - pk=3, - event=self.event, - type=RegistrationInformationField.TEXT_FIELD, - name="test text", - required=False, - ) - - registration = EventRegistration.objects.create( - event=self.event, member=self.member - ) - BooleanRegistrationInformation.objects.create( - registration=registration, field=field1, value=True - ) - IntegerRegistrationInformation.objects.create( - registration=registration, field=field2, value=42 - ) - TextRegistrationInformation.objects.create( - registration=registration, field=field3, value="text" - ) - - # as if there is a csrf token - response = self.client.get( - f"/api/v1/registrations/{registration.pk}/", follow=True - ) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.data["member"], self.member.pk) - - registration = self.event.eventregistration_set.first() - self.assertEqual(field1.get_value_for(registration), True) - self.assertEqual(field2.get_value_for(registration), 42) - self.assertEqual(field3.get_value_for(registration), "text") - - def test_registration_update_form_post_changes_fields(self): - self.event.registration_start = timezone.now() - datetime.timedelta(hours=1) - self.event.registration_end = timezone.now() + datetime.timedelta(hours=1) - self.event.cancel_deadline = timezone.now() + datetime.timedelta(hours=1) - self.event.save() - - field1 = RegistrationInformationField.objects.create( - pk=1, - event=self.event, - type=RegistrationInformationField.BOOLEAN_FIELD, - name="test bool", - required=False, - ) - - field2 = RegistrationInformationField.objects.create( - pk=2, - event=self.event, - type=RegistrationInformationField.INTEGER_FIELD, - name="test int", - required=False, - ) - - field3 = RegistrationInformationField.objects.create( - pk=3, - event=self.event, - type=RegistrationInformationField.TEXT_FIELD, - name="test text", - required=False, - ) - - response = self.client.post( - "/api/v1/events/1/registrations/", - { - "fields[info_field_1]": False, - "fields[info_field_2": 42, - "fields[info_field_3]": "text", - "csrf": "random", - }, - follow=True, - ) - self.assertEqual(response.status_code, 201) - self.assertEqual(response.data["member"], self.member.pk) - - registration = EventRegistration.objects.get( - event=self.event, member=self.member - ) - self.assertEqual(field1.get_value_for(registration), None) - self.assertEqual(field2.get_value_for(registration), None) - self.assertEqual(field3.get_value_for(registration), None) - - response = self.client.patch( - f"/api/v1/registrations/{registration.pk}/", - { - "fields[info_field_1]": True, - "fields[info_field_2]": 1337, - "fields[info_field_3]": "no text", - "csrf": "random", - }, - follow=True, - ) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.data["member"], self.member.pk) - - self.assertEqual(self.event.participants.count(), 1) - registration = self.event.eventregistration_set.first() - self.assertEqual(field1.get_value_for(registration), True) - self.assertEqual(field2.get_value_for(registration), 1337) - self.assertEqual(field3.get_value_for(registration), "no text") - - def test_registration_organiser(self): - reg0 = EventRegistration.objects.create(event=self.event, member=self.member) - reg1 = EventRegistration.objects.create(event=self.event, name="Test 1") - reg2 = EventRegistration.objects.create(event=self.event, name="Test 2") - - self.member.is_superuser = True - self.member.save() - - response = self.client.patch( - f"/api/v1/registrations/{reg0.pk}/", - {"csrf": "random", "present": True, "payment": "cash_payment"}, - follow=True, - ) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.data["member"], self.member.pk) - reg0.refresh_from_db() - self.assertIsNotNone(reg0.payment_id) - self.assertTrue(reg0.present) - - response = self.client.patch( - f"/api/v1/registrations/{reg1.pk}/", - {"csrf": "random", "present": True, "payment": "card_payment"}, - follow=True, - ) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.data["member"], None) - self.assertEqual(response.data["name"], "Test 1") - reg1.refresh_from_db() - self.assertEqual(reg1.payment.type, "card_payment") - self.assertTrue(reg1.present) - - response = self.client.patch( - f"/api/v1/registrations/{reg2.pk}/", - {"csrf": "random", "present": False, "payment": "cash_payment"}, - follow=True, - ) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.data["member"], None) - self.assertEqual(response.data["name"], "Test 2") - reg2.refresh_from_db() - self.assertEqual(reg2.payment.type, "cash_payment") - self.assertFalse(reg2.present) - def test_mark_present_url_registered(self): registration = EventRegistration.objects.create( event=self.event, @@ -547,3 +227,17 @@ def test_event_list(self): response = self.client.get("/api/v2/events/", format="json") self.assertEqual(response.data["count"], 1) self.assertEqual(response.data["results"][0]["title"], "testevent") + + def test_event_detail(self): + response = self.client.get("/api/v2/events/1/", format="json") + self.assertEqual(response.data["title"], "testevent") + + def test_event_detail_not_found(self): + response = self.client.get("/api/v2/events/2/", format="json") + self.assertEqual(response.status_code, 404) + + def test_event_detail_unpublished(self): + self.event.published = False + self.event.save() + response = self.client.get("/api/v2/events/1/", format="json") + self.assertEqual(response.status_code, 404) diff --git a/website/members/api/v1/__init__.py b/website/members/api/v1/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/website/members/api/v1/serializers.py b/website/members/api/v1/serializers.py deleted file mode 100644 index 450ee560e..000000000 --- a/website/members/api/v1/serializers.py +++ /dev/null @@ -1,185 +0,0 @@ -from django.templatetags.static import static - -from rest_framework import serializers - -from members.models import Member, Profile -from members.services import member_achievements, member_societies -from thaliawebsite.api.services import create_image_thumbnail_dict -from thaliawebsite.api.v1.cleaned_model_serializer import CleanedModelSerializer - - -class ProfileRetrieveSerializer(CleanedModelSerializer): - """Serializer that renders a member profile.""" - - class Meta: - model = Profile - fields = ( - "pk", - "display_name", - "avatar", - "profile_description", - "birthday", - "starting_year", - "programme", - "website", - "membership_type", - "achievements", - "societies", - ) - - pk = serializers.SerializerMethodField("_pk") - avatar = serializers.SerializerMethodField("_avatar") - birthday = serializers.SerializerMethodField("_birthday") - membership_type = serializers.SerializerMethodField("_membership_type") - achievements = serializers.SerializerMethodField("_achievements") - societies = serializers.SerializerMethodField("_societies") - - def _pk(self, instance): - return instance.user.pk - - def _birthday(self, instance): - if instance.show_birthday: - return instance.birthday - return None - - def _membership_type(self, instance): - membership = instance.user.current_membership - if membership: - return membership.type - return None - - def _achievements(self, instance): - return member_achievements(instance.user) - - def _societies(self, instance): - return member_societies(instance.user) - - def _avatar(self, instance): - placeholder = self.context["request"].build_absolute_uri( - static("members/images/default-avatar.jpg") - ) - file = None - if instance.photo: - file = instance.photo - return create_image_thumbnail_dict( - file, placeholder=placeholder, size_large="avatar_large" - ) - - -class MemberListSerializer(serializers.ModelSerializer): - """Serializer that renders a list of members.""" - - class Meta: - model = Member - fields = ("pk", "starting_year", "display_name", "membership_type", "avatar") - - display_name = serializers.SerializerMethodField("_display_name") - starting_year = serializers.SerializerMethodField("_starting_year") - avatar = serializers.SerializerMethodField("_avatar") - membership_type = serializers.SerializerMethodField("_membership_type") - - def _display_name(self, instance): - return instance.profile.display_name() - - def _starting_year(self, instance): - return instance.profile.starting_year - - def _avatar(self, instance): - placeholder = self.context["request"].build_absolute_uri( - static("members/images/default-avatar.jpg") - ) - file = None - if instance.profile.photo: - file = instance.profile.photo - return create_image_thumbnail_dict( - file, placeholder=placeholder, size_large="avatar_large" - ) - - def _membership_type(self, instance): - membership = instance.current_membership - if membership: - return membership.type - return None - - -class ProfileEditSerializer(CleanedModelSerializer): - """Serializer that renders a profile to be edited.""" - - class Meta: - model = Profile - fields = ( - "pk", - "email", - "first_name", - "last_name", - "address_street", - "address_street2", - "address_postal_code", - "address_city", - "address_country", - "phone_number", - "show_birthday", - "website", - "photo", - "emergency_contact", - "emergency_contact_phone_number", - "profile_description", - "nickname", - "display_name_preference", - "receive_optin", - "receive_newsletter", - "display_name", - "avatar", - "birthday", - "starting_year", - "programme", - "membership_type", - "achievements", - "societies", - ) - - read_only_fields = ("display_name", "starting_year", "programme", "birthday") - - pk = serializers.SerializerMethodField("_pk") - email = serializers.SerializerMethodField("_email") - first_name = serializers.SerializerMethodField("_first_name") - last_name = serializers.SerializerMethodField("_last_name") - avatar = serializers.SerializerMethodField("_avatar") - membership_type = serializers.SerializerMethodField("_membership_type") - achievements = serializers.SerializerMethodField("_achievements") - societies = serializers.SerializerMethodField("_societies") - - def _pk(self, instance): - return instance.user.pk - - def _email(self, instance): - return instance.user.email - - def _first_name(self, instance): - return instance.user.first_name - - def _last_name(self, instance): - return instance.user.last_name - - def _membership_type(self, instance): - membership = instance.user.current_membership - if membership: - return membership.type - return None - - def _achievements(self, instance): - return member_achievements(instance.user) - - def _societies(self, instance): - return member_societies(instance.user) - - def _avatar(self, instance): - placeholder = self.context["request"].build_absolute_uri( - static("members/images/default-avatar.jpg") - ) - file = None - if instance.photo: - file = instance.photo - return create_image_thumbnail_dict( - file, placeholder=placeholder, size_large="avatar_large" - ) diff --git a/website/members/api/v1/urls.py b/website/members/api/v1/urls.py deleted file mode 100644 index f2dc2fdbd..000000000 --- a/website/members/api/v1/urls.py +++ /dev/null @@ -1,7 +0,0 @@ -from rest_framework import routers - -from . import viewsets - -router = routers.SimpleRouter() -router.register(r"members", viewsets.MemberViewset) -urlpatterns = router.urls diff --git a/website/members/api/v1/viewsets.py b/website/members/api/v1/viewsets.py deleted file mode 100644 index 6c2b817e0..000000000 --- a/website/members/api/v1/viewsets.py +++ /dev/null @@ -1,69 +0,0 @@ -"""DRF viewsets defined by the members package.""" - -from oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope -from rest_framework import filters, mixins, permissions, viewsets - -from members.models import Member - -from .serializers import ( - MemberListSerializer, - ProfileEditSerializer, - ProfileRetrieveSerializer, -) - - -class MemberViewset(viewsets.ReadOnlyModelViewSet, mixins.UpdateModelMixin): - """Viewset that renders or edits a member.""" - - required_scopes = ["members:read"] - queryset = Member.objects.all() - filter_backends = ( - filters.OrderingFilter, - filters.SearchFilter, - ) - ordering_fields = ("profile__starting_year", "first_name", "last_name") - search_fields = ("profile__nickname", "first_name", "last_name", "username") - lookup_field = "pk" - - def get_serializer_class(self): - if self.action == "retrieve": - if self.is_self_reference() or ( - self.request.user - and self.request.user.has_perm("members.change_profile") - ): - return ProfileEditSerializer - return ProfileRetrieveSerializer - if self.action.endswith("update"): - return ProfileEditSerializer - return MemberListSerializer - - def get_queryset(self): - if self.action == "list": - return Member.current_members.get_queryset() - return Member.objects.all() - - def is_self_reference(self): - lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field - lookup_arg = self.kwargs.get(lookup_url_kwarg) - - return ( - self.request.user - and self.request.user.is_authenticated - and lookup_arg - in ( - "me", - str(self.request.member.pk), - ) - ) - - def get_permissions(self): - if self.action and ( - not self.action.endswith("update") or self.is_self_reference() - ): - return [IsAuthenticatedOrTokenHasScope()] - return [IsAuthenticatedOrTokenHasScope(), permissions.DjangoModelPermissions()] - - def get_object(self): - if self.is_self_reference(): - return self.request.member.profile - return super().get_object().profile diff --git a/website/partners/api/v1/__init__.py b/website/partners/api/v1/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/website/partners/api/v1/serializers.py b/website/partners/api/v1/serializers.py deleted file mode 100644 index 5b39174a5..000000000 --- a/website/partners/api/v1/serializers.py +++ /dev/null @@ -1,44 +0,0 @@ -from html import unescape - -from django.utils.html import strip_tags - -from rest_framework import serializers - -from events.models.external_event import ExternalEvent -from partners.models import Partner -from thaliawebsite.api.v1.cleaned_model_serializer import CleanedModelSerializer - - -class PartnerSerializer(CleanedModelSerializer): - """Partner serializer.""" - - class Meta: - """Meta class for partner serializer.""" - - model = Partner - fields = ( - "pk", - "name", - "link", - "company_profile", - "address", - "zip_code", - "city", - "logo", - ) - - -class PartnerEventSerializer(CleanedModelSerializer): - """Partner events serializer.""" - - class Meta: - """Meta class for partner events serializer.""" - - model = ExternalEvent - fields = ("pk", "title", "description", "start", "end", "location", "url") - - description = serializers.SerializerMethodField("_description") - - def _description(self, instance): - """Return description of partner event.""" - return unescape(strip_tags(instance.description)) diff --git a/website/partners/api/v1/urls.py b/website/partners/api/v1/urls.py deleted file mode 100644 index 62d852d94..000000000 --- a/website/partners/api/v1/urls.py +++ /dev/null @@ -1,8 +0,0 @@ -from rest_framework import routers - -from . import viewsets - -router = routers.SimpleRouter() -router.register(r"partners/events", viewsets.PartnerEventViewset) -router.register(r"partners", viewsets.PartnerViewset) -urlpatterns = router.urls diff --git a/website/partners/api/v1/viewsets.py b/website/partners/api/v1/viewsets.py deleted file mode 100644 index c51a6ba98..000000000 --- a/website/partners/api/v1/viewsets.py +++ /dev/null @@ -1,26 +0,0 @@ -from django.utils import timezone - -from rest_framework import filters, viewsets -from rest_framework.permissions import IsAuthenticated - -from events.models.external_event import ExternalEvent -from partners.models import Partner - -from .serializers import PartnerEventSerializer, PartnerSerializer - - -class PartnerViewset(viewsets.ReadOnlyModelViewSet): - """View set for partners.""" - - serializer_class = PartnerSerializer - queryset = Partner.objects.filter(is_active=True) - - -class PartnerEventViewset(viewsets.ReadOnlyModelViewSet): - """View set for partner events.""" - - queryset = ExternalEvent.objects.filter(end__gte=timezone.now(), published=True) - permission_classes = [IsAuthenticated] - filter_backends = (filters.OrderingFilter,) - ordering_fields = ("start", "end") - serializer_class = PartnerEventSerializer diff --git a/website/payments/api/v1/__init__.py b/website/payments/api/v1/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/website/payments/api/v1/fields.py b/website/payments/api/v1/fields.py deleted file mode 100644 index bc44b9436..000000000 --- a/website/payments/api/v1/fields.py +++ /dev/null @@ -1,14 +0,0 @@ -from rest_framework import serializers - - -class PaymentTypeField(serializers.ChoiceField): - NO_PAYMENT = "no_payment" - - def __init__(self, choices, **kwargs): - choices = choices + (self.NO_PAYMENT,) - super().__init__(choices, **kwargs) - - def get_attribute(self, instance): - if not instance.payment: - return self.NO_PAYMENT - return super().get_attribute(instance) diff --git a/website/payments/api/v1/serializers.py b/website/payments/api/v1/serializers.py deleted file mode 100644 index 64cc9e4bd..000000000 --- a/website/payments/api/v1/serializers.py +++ /dev/null @@ -1,19 +0,0 @@ -from rest_framework.fields import CharField -from rest_framework.serializers import Serializer - -from payments.models import Payment -from thaliawebsite.api.v1.cleaned_model_serializer import CleanedModelSerializer - - -class PaymentSerializer(CleanedModelSerializer): - class Meta: - model = Payment - fields = ["pk", "get_type_display", "amount", "created_at", "topic", "notes"] - - -class PaymentCreateSerializer(Serializer): - """Serializer for create payments data.""" - - app_label = CharField() - model_name = CharField() - payable_pk = CharField() # A pk can be either a UUID or an integer diff --git a/website/payments/api/v1/urls.py b/website/payments/api/v1/urls.py deleted file mode 100644 index cf55aa50a..000000000 --- a/website/payments/api/v1/urls.py +++ /dev/null @@ -1,7 +0,0 @@ -from rest_framework import routers - -from payments.api.v1 import viewsets - -router = routers.SimpleRouter() -router.register(r"payments", viewsets.PaymentViewset) -urlpatterns = router.urls diff --git a/website/payments/api/v1/viewsets/__init__.py b/website/payments/api/v1/viewsets/__init__.py deleted file mode 100644 index b4d4b0f97..000000000 --- a/website/payments/api/v1/viewsets/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .payments import PaymentViewset - -__all__ = [ - "PaymentViewset", -] diff --git a/website/payments/api/v1/viewsets/payments.py b/website/payments/api/v1/viewsets/payments.py deleted file mode 100644 index 3467b1a9e..000000000 --- a/website/payments/api/v1/viewsets/payments.py +++ /dev/null @@ -1,74 +0,0 @@ -from django.apps import apps -from django.urls import reverse - -from rest_framework import status -from rest_framework.exceptions import PermissionDenied, ValidationError -from rest_framework.mixins import ListModelMixin, RetrieveModelMixin -from rest_framework.permissions import IsAuthenticated -from rest_framework.response import Response -from rest_framework.viewsets import GenericViewSet - -from payments import services -from payments.api.v1.serializers import PaymentCreateSerializer, PaymentSerializer -from payments.exceptions import PaymentError -from payments.models import Payment, PaymentUser -from payments.payables import payables - - -class PaymentViewset(ListModelMixin, RetrieveModelMixin, GenericViewSet): - """The viewset to list retrieve and create payments.""" - - queryset = Payment.objects.all() - permission_classes = [IsAuthenticated] - - def get_serializer_class(self): - if self.action == "create": - return PaymentCreateSerializer - return PaymentSerializer - - def get_queryset(self): - user = self.request.user - return Payment.objects.filter(paid_by=user) - - def create(self, request): - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - app_label = serializer.data["app_label"] - model_name = serializer.data["model_name"] - payable_pk = serializer.data["payable_pk"] - - payable_model = apps.get_model(app_label=app_label, model_name=model_name) - payable = payables.get_payable(payable_model.objects.get(pk=payable_pk)) - - if ( - payable.payment_payer.pk - != PaymentUser.objects.get(pk=self.request.user.pk).pk - ): - raise PermissionDenied( - detail="You are not allowed to process this payment." - ) - - if payable.payment: - return Response( - data={"detail": "This object has already been paid for."}, - status=status.HTTP_409_CONFLICT, - ) - - try: - services.create_payment( - payable, - PaymentUser.objects.get(pk=request.user.pk), - Payment.TPAY, - ) - except PaymentError as e: - raise ValidationError(detail=str(e)) from e - - payable.model.refresh_from_db() - headers = { - "Location": reverse( - "api:v1:payment-detail", kwargs={"pk": payable.payment.pk} - ) - } - return Response( - serializer.data, status=status.HTTP_201_CREATED, headers=headers - ) diff --git a/website/payments/tests/api/v1/__init__.py b/website/payments/tests/api/v1/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/website/payments/tests/api/v1/test_fields.py b/website/payments/tests/api/v1/test_fields.py deleted file mode 100644 index b1f2bed29..000000000 --- a/website/payments/tests/api/v1/test_fields.py +++ /dev/null @@ -1,30 +0,0 @@ -from unittest import mock - -from django.test import TestCase, override_settings - -from freezegun import freeze_time - -from payments.api.v1.fields import PaymentTypeField -from payments.models import Payment - - -@freeze_time("2019-01-01") -@override_settings(SUSPEND_SIGNALS=True) -class PaymentTypeFieldTest(TestCase): - """Test for the payment type field.""" - - fixtures = ["members.json"] - - @mock.patch("rest_framework.serializers.ChoiceField.get_attribute") - def test_get_attribute(self, mock_super): - field = PaymentTypeField(choices=Payment.PAYMENT_TYPE) - - obj = Payment() - obj.payment = False - - self.assertEqual(field.get_attribute(obj), PaymentTypeField.NO_PAYMENT) - - obj.payment = True - - field.get_attribute(obj) - mock_super.assert_called() diff --git a/website/payments/tests/api/v1/test_viewsets_payments.py b/website/payments/tests/api/v1/test_viewsets_payments.py deleted file mode 100644 index cdf61b394..000000000 --- a/website/payments/tests/api/v1/test_viewsets_payments.py +++ /dev/null @@ -1,138 +0,0 @@ -from unittest import mock -from unittest.mock import MagicMock, Mock - -from django.apps import apps -from django.test import TestCase, override_settings -from django.urls import reverse - -from freezegun import freeze_time -from rest_framework.test import APIClient - -from members.models import Member -from payments.exceptions import PaymentError -from payments.models import BankAccount, Payment, PaymentUser -from payments.payables import payables -from payments.tests.__mocks__ import MockModel, MockPayable - - -@freeze_time("2020-09-01") -@override_settings(SUSPEND_SIGNALS=True, THALIA_PAY_ENABLED_PAYMENT_METHOD=True) -class PaymentProcessViewTest(TestCase): - """Test for the PaymentProcessView.""" - - fixtures = ["members.json"] - - test_body = { - "app_label": "mock_app", - "model_name": "mock_model", - "payable_pk": "mock_payable_pk", - } - - @classmethod - def setUpTestData(cls): - cls.user = Member.objects.filter(last_name="Wiggers").first() - cls.account1 = BankAccount.objects.create( - owner=cls.user, - initials="J1", - last_name="Test", - iban="NL91ABNA0417164300", - valid_from="2019-03-01", - signature="sig", - mandate_no="11-2", - ) - cls.user = PaymentUser.objects.get(pk=cls.user.pk) - - def setUp(self): - payables.register(MockModel, MockPayable) - - self.account1.refresh_from_db() - self.client = APIClient() - self.client.force_login(self.user) - - self.payable = MockPayable(MockModel(payer=self.user)) - self.original_get_payable = payables.get_payable - payables.get_payable = MagicMock() - payables.get_payable.return_value = self.payable - - self.original_get_model = apps.get_model - mock_get_model = MagicMock() - - def side_effect(*args, **kwargs): - if "app_label" in kwargs and kwargs["app_label"] == "mock_app": - return mock_get_model - return self.original_get_model(*args, **kwargs) - - apps.get_model = Mock(side_effect=side_effect) - - def tearDown(self): - apps.get_model = self.original_get_model - payables.get_payable = self.original_get_payable - payables._unregister(MockModel) - - def test_not_logged_in(self): - self.client.logout() - - response = self.client.post(reverse("api:v1:payment-list")) - self.assertEqual(403, response.status_code) - - @override_settings(THALIA_PAY_ENABLED_PAYMENT_METHOD=False) - def test_member_has_tpay_enabled(self): - response = self.client.post( - reverse("api:v1:payment-list"), self.test_body, format="json" - ) - self.assertEqual(400, response.status_code) - - def test_different_member(self): - self.payable.model.payer = PaymentUser() - - response = self.client.post( - reverse("api:v1:payment-list"), self.test_body, format="json" - ) - - self.assertEqual(403, response.status_code) - self.assertEqual( - {"detail": "You are not allowed to process this payment."}, response.data - ) - - def test_already_paid(self): - self.payable.model.payment = Payment(amount=8) - - response = self.client.post( - reverse("api:v1:payment-list"), self.test_body, format="json" - ) - - self.assertEqual(409, response.status_code) - self.assertEqual( - {"detail": "This object has already been paid for."}, response.data - ) - - @mock.patch("payments.services.create_payment") - @mock.patch("payments.payables.payables.get_payable") - def test_creates_payment(self, get_payable, create_payment): - def set_payments_side_effect(*args, **kwargs): - self.payable.model.payment = Payment.objects.create(amount=8) - - create_payment.side_effect = set_payments_side_effect - get_payable.return_value = self.payable - - response = self.client.post( - reverse("api:v1:payment-list"), self.test_body, format="json" - ) - - create_payment.assert_called_with(self.payable, self.user, Payment.TPAY) - - self.assertEqual(201, response.status_code) - self.assertEqual( - reverse("api:v1:payment-detail", kwargs={"pk": self.payable.payment.pk}), - response.headers["Location"], - ) - - @mock.patch("payments.services.create_payment") - def test_payment_create_error(self, create_payment): - create_payment.side_effect = PaymentError("Test error") - - response = self.client.post( - reverse("api:v1:payment-list"), self.test_body, format="json" - ) - - self.assertEqual(400, response.status_code) diff --git a/website/photos/api/v1/__init__.py b/website/photos/api/v1/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/website/photos/api/v1/serializers.py b/website/photos/api/v1/serializers.py deleted file mode 100644 index 07949047e..000000000 --- a/website/photos/api/v1/serializers.py +++ /dev/null @@ -1,89 +0,0 @@ -from rest_framework import serializers - -from photos import services -from photos.models import Album, Photo -from thaliawebsite.api.services import create_image_thumbnail_dict -from thaliawebsite.api.v1.cleaned_model_serializer import CleanedModelSerializer - - -class PhotoRetrieveSerializer(CleanedModelSerializer): - """ModelSerializer class to get a Photo object set.""" - - file = serializers.SerializerMethodField("_file") - - def _file(self, obj): - file = None - if obj: - file = obj.file - return create_image_thumbnail_dict(file) - - class Meta: - """Meta class for PhotoRetrieveSerializer.""" - - model = Photo - fields = ("pk", "album", "file") - - -class PhotoCreateSerializer(CleanedModelSerializer): - """ModelSerializer class to create or update a Photo object set.""" - - class Meta: - """Met class for PhotoCreateSerializer.""" - - model = Photo - fields = ("pk", "album", "file") - - -class AlbumSerializer(CleanedModelSerializer): - """ModelSerializer for an Album object.""" - - photos = serializers.SerializerMethodField("_photos") - accessible = serializers.SerializerMethodField("_accessible") - - def _accessible(self, obj): - return services.is_album_accessible(self.context["request"], obj) - - def _photos(self, obj): - if self._accessible(obj): - return PhotoRetrieveSerializer( - obj.photo_set, context=self.context, many=True - ).data - return [] - - def create(self, validated_data): - """Create album.""" - photos_data = validated_data.pop("photos") - album = Album.objects.create(**validated_data) - for photo_data in photos_data: - Photo.objects.create(album=album, **photo_data) - return album - - def update(self, instance, validated_data): - """Update album.""" - photos_data = validated_data.pop("photos") - album = Album.objects.update(**validated_data) - for photo_data in photos_data: - Photo.objects.update(album=album, **photo_data) - return album - - class Meta: - """Meta class for AlbumSerializer.""" - - model = Album - fields = ("pk", "title", "date", "hidden", "shareable", "accessible", "photos") - - -class AlbumListSerializer(CleanedModelSerializer): - """ModelSerializer class for a list of Albums.""" - - cover = PhotoRetrieveSerializer() - accessible = serializers.SerializerMethodField("_accessible") - - def _accessible(self, obj): - return services.is_album_accessible(self.context["request"], obj) - - class Meta: - """Meta class for AlbumListSerializer.""" - - model = Album - fields = ("pk", "title", "date", "hidden", "shareable", "accessible", "cover") diff --git a/website/photos/api/v1/urls.py b/website/photos/api/v1/urls.py deleted file mode 100644 index c9b91320a..000000000 --- a/website/photos/api/v1/urls.py +++ /dev/null @@ -1,8 +0,0 @@ -from rest_framework import routers - -from . import viewsets - -router = routers.SimpleRouter() -router.register(r"photos/images", viewsets.PhotosViewSet) -router.register(r"photos/albums", viewsets.AlbumsViewSet) -urlpatterns = router.urls diff --git a/website/photos/api/v1/viewsets.py b/website/photos/api/v1/viewsets.py deleted file mode 100644 index 776e5c7bc..000000000 --- a/website/photos/api/v1/viewsets.py +++ /dev/null @@ -1,61 +0,0 @@ -from rest_framework import filters, permissions -from rest_framework.exceptions import PermissionDenied -from rest_framework.mixins import CreateModelMixin, UpdateModelMixin -from rest_framework.viewsets import GenericViewSet, ModelViewSet - -from photos import services -from photos.api.v1 import serializers -from photos.models import Album, Photo - - -class AlbumsViewSet(ModelViewSet): - """ViewSet class for an Album object.""" - - permission_classes = (permissions.IsAuthenticated,) - queryset = Album.objects.all() - filter_backends = (filters.SearchFilter,) - search_fields = ("title", "date", "slug") - - def get_queryset(self): - """Return albums that are annotated to be accessible by the request user.""" - return services.get_annotated_accessible_albums( - self.request, Album.objects.all() - ) - - def create(self, request, *args, **kwargs): - """Create album if the request user is allowed to.""" - if self.request.user.has_perm("photos.create_album"): - return super().create(request, *args, **kwargs) - raise PermissionDenied - - def update(self, request, *args, **kwargs): - """Create album if the request user is allowed to.""" - if self.request.user.has_perm("photos.change_album"): - return super().update(request, *args, **kwargs) - raise PermissionDenied - - def get_serializer_class(self): - """Return AlbumListSerializer if the current action is list else return AlbumSerializer.""" - if self.action == "list": - return serializers.AlbumListSerializer - return serializers.AlbumSerializer - - -class PhotosViewSet(GenericViewSet, CreateModelMixin, UpdateModelMixin): - """ViewSet class for a Photo object.""" - - queryset = Photo.objects.all() - permission_classes = (permissions.IsAuthenticated,) - serializer_class = serializers.PhotoCreateSerializer - - def create(self, request, *args, **kwargs): - """Create photo if the request user is allowed to.""" - if self.request.user.has_perm("photos.create_photo"): - return super().create(request, *args, **kwargs) - raise PermissionDenied - - def update(self, request, *args, **kwargs): - """Update photo if the request user is allowed to.""" - if self.request.user.has_perm("photos.change_photo"): - return super().update(request, *args, **kwargs) - raise PermissionDenied diff --git a/website/pizzas/api/v1/__init__.py b/website/pizzas/api/v1/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/website/pizzas/api/v1/serializers.py b/website/pizzas/api/v1/serializers.py deleted file mode 100644 index ed11d0748..000000000 --- a/website/pizzas/api/v1/serializers.py +++ /dev/null @@ -1,81 +0,0 @@ -from typing import Any - -from django.db.models import Model -from django.utils.translation import gettext_lazy as _ - -from rest_framework import serializers -from rest_framework.exceptions import ValidationError - -from payments.api.v1.fields import PaymentTypeField -from payments.models import Payment -from pizzas.models import FoodEvent, FoodOrder, Product -from pizzas.services import can_change_order -from thaliawebsite.api.v1.cleaned_model_serializer import CleanedModelSerializer - - -class PizzaSerializer(CleanedModelSerializer): - class Meta: - model = Product - fields = ("pk", "name", "description", "price", "available") - - -class PizzaEventSerializer(CleanedModelSerializer): - class Meta: - model = FoodEvent - fields = ("start", "end", "event", "title", "is_admin") - - event = serializers.PrimaryKeyRelatedField(read_only=True) - is_admin = serializers.SerializerMethodField("_is_admin") - - def _is_admin(self, instance): - member = self.context["request"].member - return can_change_order(member, instance) - - -class OrderSerializer(CleanedModelSerializer): - class Meta: - model = FoodOrder - fields = ("pk", "payment", "product", "name", "member") - read_only_fields = ("pk", "payment", "name", "member") - - payment = PaymentTypeField( - source="payment.type", choices=Payment.PAYMENT_TYPE, read_only=True - ) - - -class AdminOrderSerializer(CleanedModelSerializer): - class Meta: - model = FoodOrder - fields = ("pk", "payment", "product", "name", "member", "display_name") - - payment = PaymentTypeField( - source="payment.type", choices=Payment.PAYMENT_TYPE, required=False - ) - display_name = serializers.SerializerMethodField("_display_name") - - def _display_name(self, instance): - if instance.member: - return instance.member.get_full_name() - return instance.name - - def validate(self, attrs): - if attrs.get("member") and attrs.get("name"): - raise ValidationError( - { - "member": _("Either specify a member or a name"), - "name": _("Either specify a member or a name"), - } - ) - if not (attrs.get("member") or attrs.get("name")) and not self.partial: - attrs["member"] = self.context["request"].member - return super().validate(attrs) - - def create(self, validated_data: Any) -> Any: - if "payment" in validated_data: - del validated_data["payment"] - return super().create(validated_data) - - def update(self, instance: Model, validated_data: Any) -> Any: - if "payment" in validated_data: - del validated_data["payment"] - return super().update(instance, validated_data) diff --git a/website/pizzas/api/v1/urls.py b/website/pizzas/api/v1/urls.py deleted file mode 100644 index d6a690330..000000000 --- a/website/pizzas/api/v1/urls.py +++ /dev/null @@ -1,8 +0,0 @@ -from rest_framework import routers - -from . import viewsets - -router = routers.SimpleRouter() -router.register(r"pizzas", viewsets.PizzaViewset) -router.register(r"pizzas/orders", viewsets.OrderViewset) -urlpatterns = router.urls diff --git a/website/pizzas/api/v1/viewsets.py b/website/pizzas/api/v1/viewsets.py deleted file mode 100644 index 2547a68f4..000000000 --- a/website/pizzas/api/v1/viewsets.py +++ /dev/null @@ -1,119 +0,0 @@ -from django.db import IntegrityError - -from rest_framework import permissions -from rest_framework.decorators import action -from rest_framework.exceptions import NotFound, PermissionDenied, ValidationError -from rest_framework.generics import get_object_or_404 -from rest_framework.mixins import ListModelMixin -from rest_framework.response import Response -from rest_framework.viewsets import GenericViewSet, ModelViewSet - -from payments.api.v1.fields import PaymentTypeField -from payments.services import create_payment, delete_payment -from pizzas.models import FoodEvent, FoodOrder, Product -from pizzas.services import can_change_order - -from . import serializers - - -class PizzaViewset(GenericViewSet, ListModelMixin): - queryset = Product.available_products.all() - permission_classes = (permissions.IsAuthenticated,) - serializer_class = serializers.PizzaSerializer - - def list(self, request, *args, **kwargs): - if FoodEvent.current() or request.user.has_perm("pizzas.change_product"): - queryset = self.get_queryset() - if not request.user.has_perm("pizzas.order_restricted_products"): - queryset = queryset.exclude(restricted=True) - serializer = serializers.PizzaSerializer(queryset, many=True) - return Response(serializer.data) - raise PermissionDenied - - @action(detail=False) - def event(self, request): - event = FoodEvent.current() - - if event: - context = {"request": request} - serializer = serializers.PizzaEventSerializer(event, context=context) - return Response(serializer.data) - - raise NotFound - - -class OrderViewset(ModelViewSet): - permission_classes = (permissions.IsAuthenticated,) - queryset = FoodOrder.objects.all() - - def get_queryset(self): - event = FoodEvent.current() - if can_change_order(self.request.member, event): - return FoodOrder.objects.filter(food_event=event) - if self.action in ("update", "destroy"): - if not event or event.has_ended: - return FoodOrder.objects.none() - - return FoodOrder.objects.filter( - member=self.request.member, - payment=None, - food_event=event, - ) - return FoodOrder.objects.filter(member=self.request.member, food_event=event) - - def get_serializer_class(self): - event = FoodEvent.current() - if can_change_order(self.request.member, event): - return serializers.AdminOrderSerializer - return serializers.OrderSerializer - - def get_object(self): - if self.kwargs.get(self.lookup_field) == "me": - order = get_object_or_404( - self.get_queryset(), - member=self.request.member, - food_event=FoodEvent.current(), - ) - self.check_object_permissions(self.request, order) - return order - return super().get_object() - - def perform_create(self, serializer): - try: - if serializer.validated_data.get("name"): - serializer.save(food_event=FoodEvent.current()) - elif can_change_order(self.request.member, FoodEvent.current()): - order = serializer.save(food_event=FoodEvent.current()) - if "payment" in serializer.validated_data: - payment_type = serializer.validated_data["payment"]["type"] - else: - payment_type = PaymentTypeField.NO_PAYMENT - - self._update_payment(order, payment_type, self.request.user) - else: - serializer.save( - member=self.request.member, food_event=FoodEvent.current() - ) - except IntegrityError as e: - raise ValidationError( - "Something went wrong when saving the order" + str(e) - ) from e - - def perform_update(self, serializer): - order = serializer.save() - if "payment" in serializer.validated_data and can_change_order( - self.request.member, FoodEvent.current() - ): - self._update_payment( - order, - serializer.validated_data["payment"]["type"], - self.request.user, - ) - - @staticmethod - def _update_payment(order, payment_type=None, processed_by=None): - if order.payment and payment_type == PaymentTypeField.NO_PAYMENT: - delete_payment(order, processed_by) - elif payment_type != PaymentTypeField.NO_PAYMENT: - order.payment = create_payment(order, processed_by, payment_type) - order.save() diff --git a/website/pushnotifications/api/v1/__init__.py b/website/pushnotifications/api/v1/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/website/pushnotifications/api/v1/permissions.py b/website/pushnotifications/api/v1/permissions.py deleted file mode 100644 index ac49f0918..000000000 --- a/website/pushnotifications/api/v1/permissions.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.db.models import QuerySet - -from rest_framework import permissions - - -class IsOwner(permissions.BasePermission): - def has_object_permission(self, request, view, obj): - if isinstance(obj, QuerySet): - return True - # must be the owner to view the object - return obj.user == request.user diff --git a/website/pushnotifications/api/v1/serializers.py b/website/pushnotifications/api/v1/serializers.py deleted file mode 100644 index a67edf1d0..000000000 --- a/website/pushnotifications/api/v1/serializers.py +++ /dev/null @@ -1,43 +0,0 @@ -from rest_framework.relations import ManyRelatedField, PrimaryKeyRelatedField - -from pushnotifications.models import Category, Device, Message -from thaliawebsite.api.v1.cleaned_model_serializer import CleanedModelSerializer - - -class DeviceSerializer(CleanedModelSerializer): - receive_category = ManyRelatedField( - allow_empty=True, - required=False, - child_relation=PrimaryKeyRelatedField( - allow_empty=True, queryset=Category.objects.all(), required=False - ), - ) - - class Meta: - model = Device - - fields = ( - "pk", - "registration_id", - "active", - "date_created", - "type", - "receive_category", - ) - read_only_fields = ("date_created",) - - extra_kwargs = {"active": {"default": True}} - - -class CategorySerializer(CleanedModelSerializer): - class Meta: - model = Category - - fields = ("key", "name", "description") - - -class MessageSerializer(CleanedModelSerializer): - class Meta: - model = Message - - fields = ("pk", "title", "body", "url", "category", "sent") diff --git a/website/pushnotifications/api/v1/urls.py b/website/pushnotifications/api/v1/urls.py deleted file mode 100644 index df3b806d2..000000000 --- a/website/pushnotifications/api/v1/urls.py +++ /dev/null @@ -1,8 +0,0 @@ -from rest_framework import routers - -from pushnotifications.api.v1 import viewsets - -router = routers.SimpleRouter() -router.register(r"devices", viewsets.DeviceViewSet) -router.register(r"notifications", viewsets.MessageViewSet) -urlpatterns = router.urls diff --git a/website/pushnotifications/api/v1/viewsets.py b/website/pushnotifications/api/v1/viewsets.py deleted file mode 100644 index d515beb29..000000000 --- a/website/pushnotifications/api/v1/viewsets.py +++ /dev/null @@ -1,61 +0,0 @@ -from rest_framework import filters, permissions, viewsets -from rest_framework.decorators import action -from rest_framework.response import Response - -from pushnotifications.models import Category, Device, Message - -from .permissions import IsOwner -from .serializers import CategorySerializer, DeviceSerializer, MessageSerializer - - -class DeviceViewSet(viewsets.ModelViewSet): - permission_classes = (permissions.IsAuthenticated, IsOwner) - queryset = Device.objects.all() - serializer_class = DeviceSerializer - - def get_queryset(self): - # filter all devices to only those belonging to the current user - return self.queryset.filter(user=self.request.user) - - def perform_create(self, serializer): - try: - serializer.instance = Device.objects.get( - user=self.request.user, - registration_id=serializer.validated_data["registration_id"], - ) - except Device.DoesNotExist: - pass - data = serializer.validated_data - if "receive_category" in data and len(data["receive_category"]) > 0: - categories = data["receive_category"] + ["general"] - serializer.save(user=self.request.user, receive_category=categories) - else: - categories = [c.pk for c in Category.objects.all()] - serializer.save(user=self.request.user, receive_category=categories) - - def perform_update(self, serializer): - serializer.save(user=self.request.user) - - @action(detail=False) - def categories(self, request): - categories = Category.objects.all() - serializer = CategorySerializer(categories, many=True) - return Response(serializer.data) - - -class MessageViewSet(viewsets.ReadOnlyModelViewSet): - permission_classes = (permissions.IsAuthenticated,) - queryset = Message.objects.all() - filter_backends = (filters.OrderingFilter,) - ordering_fields = ("sent",) - serializer_class = MessageSerializer - - def get_queryset(self): - queryset = Message.all_objects.filter( - users=self.request.user, sent__isnull=False - ) - - category = self.request.query_params.get("category", None) - if category is not None: - return queryset.filter(category=category) - return queryset diff --git a/website/thaliawebsite/api/authentication.py b/website/thaliawebsite/api/authentication.py deleted file mode 100644 index a877ec575..000000000 --- a/website/thaliawebsite/api/authentication.py +++ /dev/null @@ -1,10 +0,0 @@ -from rest_framework.authentication import TokenAuthentication - - -class APIv1TokenAuthentication(TokenAuthentication): - """Custom token authentication class that only works for API v1.""" - - def authenticate(self, request): - if request.version == "v2": - return None - return super().authenticate(request) diff --git a/website/thaliawebsite/api/pagination.py b/website/thaliawebsite/api/pagination.py deleted file mode 100644 index d8aec778d..000000000 --- a/website/thaliawebsite/api/pagination.py +++ /dev/null @@ -1,13 +0,0 @@ -from rest_framework.pagination import LimitOffsetPagination - - -class APIv2LimitOffsetPagination(LimitOffsetPagination): - """Pagination class that uses LimitOffsetPagination and sets the default value for the pagination size to None for API v1.""" - - def get_limit(self, request): - if self.limit_query_param in request.query_params: - return super().get_limit(request) - - if request.version == "v1": - return None - return self.default_limit diff --git a/website/thaliawebsite/api/urls.py b/website/thaliawebsite/api/urls.py index 2628e6947..11570c80e 100644 --- a/website/thaliawebsite/api/urls.py +++ b/website/thaliawebsite/api/urls.py @@ -8,7 +8,6 @@ "", include( [ - path("v1/", include("thaliawebsite.api.v1.urls", namespace="v1")), path("v2/", include("thaliawebsite.api.v2.urls", namespace="v2")), path( "calendarjs/", @@ -27,9 +26,7 @@ "docs", TemplateView.as_view( template_name="swagger/index.html", - extra_context={ - "schema_urls": ["api:v2:schema", "api:v1:schema"] - }, + extra_context={"schema_urls": ["api:v2:schema"]}, ), name="swagger", ), diff --git a/website/thaliawebsite/api/v1/__init__.py b/website/thaliawebsite/api/v1/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/website/thaliawebsite/api/v1/cleaned_model_serializer.py b/website/thaliawebsite/api/v1/cleaned_model_serializer.py deleted file mode 100644 index 0834c28d1..000000000 --- a/website/thaliawebsite/api/v1/cleaned_model_serializer.py +++ /dev/null @@ -1,11 +0,0 @@ -from rest_framework import serializers - - -class CleanedModelSerializer(serializers.ModelSerializer): - """Custom ModelSerializer that explicitly clean's a model before it is saved.""" - - def validate(self, attrs): - super().validate(attrs) - instance = self.Meta.model(**attrs) - instance.clean() - return attrs diff --git a/website/thaliawebsite/api/v1/urls.py b/website/thaliawebsite/api/v1/urls.py deleted file mode 100644 index 6581b437b..000000000 --- a/website/thaliawebsite/api/v1/urls.py +++ /dev/null @@ -1,31 +0,0 @@ -from django.conf import settings -from django.urls import include, path - -from rest_framework.schemas import get_schema_view - -from thaliawebsite.api.openapi import OAuthSchemaGenerator - -app_name = "thaliawebsite" - -urlpatterns = [ - path("", include("activemembers.api.v1.urls")), - path("", include("announcements.api.v1.urls")), - path("", include("events.api.v1.urls")), - path("", include("members.api.v1.urls")), - path("", include("partners.api.v1.urls")), - path("", include("pizzas.api.v1.urls")), - path("", include("photos.api.v1.urls")), - path("", include("pushnotifications.api.v1.urls")), - path("", include("payments.api.v1.urls")), - path( - "schema", - get_schema_view( - title="API v1", - version=settings.SOURCE_COMMIT, - url="/api/v1/", - urlconf="thaliawebsite.api.v1.urls", - generator_class=OAuthSchemaGenerator, - ), - name="schema", - ), -] diff --git a/website/thaliawebsite/settings.py b/website/thaliawebsite/settings.py index 65065e4b0..a662ad8c9 100644 --- a/website/thaliawebsite/settings.py +++ b/website/thaliawebsite/settings.py @@ -783,7 +783,7 @@ def show_toolbar(request): # Cors configuration CORS_ORIGIN_ALLOW_ALL = True -CORS_URLS_REGEX = r"^/(?:api/v1|api/v2|user/oauth)/.*" +CORS_URLS_REGEX = r"^/(?:api/v2|user/oauth)/.*" # OAuth configuration OIDC_RSA_PRIVATE_KEY = from_env("OIDC_RSA_PRIVATE_KEY", testing=None) @@ -872,12 +872,11 @@ def show_toolbar(request): REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ( "rest_framework.authentication.SessionAuthentication", - "thaliawebsite.api.authentication.APIv1TokenAuthentication", "oauth2_provider.contrib.rest_framework.OAuth2Authentication", ), - "DEFAULT_PAGINATION_CLASS": "thaliawebsite.api.pagination.APIv2LimitOffsetPagination", + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", "PAGE_SIZE": 50, # Only for API v2 - "ALLOWED_VERSIONS": ["v1", "v2", "calendarjs", "facedetection"], + "ALLOWED_VERSIONS": ["v2", "calendarjs", "facedetection"], "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.NamespaceVersioning", "DEFAULT_SCHEMA_CLASS": "thaliawebsite.api.openapi.OAuthAutoSchema", "DEFAULT_THROTTLE_CLASSES": [