diff options
Diffstat (limited to 'api')
-rw-r--r-- | api/serializers.py | 57 | ||||
-rw-r--r-- | api/urls.py | 25 | ||||
-rw-r--r-- | api/views.py | 191 |
3 files changed, 273 insertions, 0 deletions
diff --git a/api/serializers.py b/api/serializers.py new file mode 100644 index 0000000..5d81f44 --- /dev/null +++ b/api/serializers.py @@ -0,0 +1,57 @@ +# Copyright (C) 2019 Alban Gruin +# +# celcatsanitizer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# celcatsanitizer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with celcatsanitizer. If not, see <http://www.gnu.org/licenses/>. + +from rest_framework import serializers + +from ..models import Course, Group, Room, Source, Timetable, Year + + +class YearSerializer(serializers.ModelSerializer): + class Meta: + model = Year + fields = "__all__" + + +class SourceSerializer(serializers.ModelSerializer): + class Meta: + model = Source + fields = "__all__" + + +class TimetableSerializer(serializers.ModelSerializer): + class Meta: + model = Timetable + fields = "__all__" + + +class GroupSerializer(serializers.ModelSerializer): + class Meta: + model = Group + fields = "__all__" + + +class RoomSerializer(serializers.ModelSerializer): + class Meta: + model = Room + fields = "__all__" + + +class CourseSerializer(serializers.ModelSerializer): + groups = GroupSerializer(many=True, read_only=True) + rooms = RoomSerializer(many=True, read_only=True) + + class Meta: + model = Course + fields = "__all__" diff --git a/api/urls.py b/api/urls.py new file mode 100644 index 0000000..0164a4f --- /dev/null +++ b/api/urls.py @@ -0,0 +1,25 @@ +# Copyright (C) 2019 Alban Gruin +# +# celcatsanitizer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# celcatsanitizer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with celcatsanitizer. If not, see <http://www.gnu.org/licenses/>. + +from rest_framework import routers +from . import views + +router = routers.DefaultRouter() +router.register(r"years", views.YearViewSet) +router.register(r"sources", views.SourceViewSet) +router.register(r"timetables", views.TimetableViewSet) +router.register(r"rooms", views.RoomViewSet) +router.register(r"groups", views.GroupViewSet) +router.register(r"courses", views.CourseViewSet) diff --git a/api/views.py b/api/views.py new file mode 100644 index 0000000..2459455 --- /dev/null +++ b/api/views.py @@ -0,0 +1,191 @@ +# Copyright (C) 2019 Alban Gruin +# +# celcatsanitizer is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# celcatsanitizer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with celcatsanitizer. If not, see <http://www.gnu.org/licenses/>. + +import datetime + +from django.db.models import Count +from django.db.models.functions import ExtractWeek, ExtractYear +from django.utils import timezone + +from rest_framework import viewsets +from rest_framework.decorators import action +from rest_framework.response import Response + +from ..forms import QSJPSForm +from ..models import Course, Group, Room, Source, Timetable, Year +from ..utils import get_current_or_next_week, get_week + +from .serializers import CourseSerializer, GroupSerializer, RoomSerializer, \ + SourceSerializer, TimetableSerializer, YearSerializer + + +class YearViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Year.objects.all().order_by("name") + serializer_class = YearSerializer + + @action(detail=True, methods=["get"], url_path="timetables") + def timetable_list(self, request, pk): + year = self.get_object() + timetables = Timetable.objects.filter(year=year).distinct() \ + .order_by("name") + timetables_json = TimetableSerializer( + self.paginate_queryset(timetables), many=True) + return self.get_paginated_response(timetables_json.data) + + +class SourceViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Source.objects.all().order_by("pk") + serializer_class = SourceSerializer + + @action(detail=True, methods=["get"], url_path="timetables") + def timetable_list(self, request, pk): + source = self.get_object() + timetables = Timetable.objects.filter(source=source).distinct() \ + .order_by("name") + timetables_json = TimetableSerializer( + self.paginate_queryset(timetables), many=True) + return self.get_paginated_response(timetables_json.data) + + +class TimetableViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Timetable.objects.all().select_related("source") \ + .order_by("year", "name") + serializer_class = TimetableSerializer + + @action(detail=True, methods=["get"], url_path="groups") + def group_list(self, request, pk): + timetable = self.get_object() + groups = Group.objects.filter(source=timetable.source).distinct() \ + .order_by("name") + groups_json = GroupSerializer(self.paginate_queryset(groups), + many=True) + return self.get_paginated_response(groups_json.data) + + +class CourseListGroupSet(viewsets.ReadOnlyModelViewSet): + @action(detail=True, methods=["get"], url_path="courses") + def course_list(self, request, pk): + obj = self.get_object() + courses = Course.objects.get_courses(obj).prefetch_related("groups") + courses_json = CourseSerializer(self.paginate_queryset(courses), + many=True) + return self.get_paginated_response(courses_json.data) + + def __get_courses(self, obj, start, end): + courses = Course.objects.get_courses(obj, + begin__gte=start, end__lt=end) \ + .prefetch_related("groups") + courses_json = CourseSerializer(self.paginate_queryset(courses), + many=True) + return self.get_paginated_response(courses_json.data) + + @action(detail=True, methods=["get"], url_path="courses/days/current") + def current_day(self, request, pk): + obj = self.get_object() + start = datetime.date.today() + end = start + datetime.timedelta(days=1) + return self.__get_courses(obj, start, end) + + @action(detail=True, methods=["get"], + url_path="courses/days/(?P<year>\d+)/(?P<month>\d+)/(?P<day>\d+)") + def other_day(self, request, pk, year, month, day): + obj = self.get_object() + + try: + start = datetime.date(int(year), int(month), int(day)) + except ValueError as v: + errors = {} + message = v.args[0] + if message.split(" ")[0] == "month": + errors["month"] = "Rentrez un mois invalide" + else: + errors["day"] = "Numéro de jour invalide pour le mois" + return Response(errors, status=400) + + end = start + datetime.timedelta(days=1) + return self.__get_courses(obj, start, end) + + @action(detail=True, methods=["get"], url_path="courses/weeks/current") + def current_week(self, request, pk): + obj = self.get_object() + start, end = get_week(*get_current_or_next_week()) + return self.__get_courses(obj, start, end) + + @action(detail=True, methods=["get"], + url_path="courses/weeks/(?P<year>\d+)/(?P<week>\d+)") + def other_week(self, request, pk, year, week): + obj = self.get_object() + + errors = {} + if not year.isdigit(): + errors["year"] = "Rentrez une année valide" + if not week.isdigit() or not 0 < int(week) <= 53: + errors["week"] = "Rentrez une semaine valide" + if errors: + return Response(errors, status=400) + + return self.__get_courses(obj, *get_week(int(year), int(week))) + + +class GroupViewSet(CourseListGroupSet): + queryset = Group.objects.all().order_by("name") + serializer_class = GroupSerializer + + @action(detail=True, methods=["get"], url_path="courses/weeks") + def weeks(self, request, pk): + group = self.get_object() + groups = Group.objects.get_parents(group) + + courses = Course.objects.filter(groups__in=groups) \ + .order_by("year", "week") \ + .annotate(year=ExtractYear("begin"), + week=ExtractWeek("begin")) \ + .values("year", "week") \ + .annotate(c=Count("*")) + + weeks = [get_week(course["year"], course["week"])[0] + for course in courses] + + return Response(weeks) + + +class RoomViewSet(CourseListGroupSet): + queryset = Room.objects.all().order_by("name") + serializer_class = RoomSerializer + + @action( + methods=["get"], + detail=False, + url_path="qsjps/(?P<day>[0-9\-]+)/(?P<begin>[0-9:]+)/(?P<end>[0-9:]+)") + def qsjps(self, request, day, begin, end): + form = QSJPSForm({"day": day, "begin": begin, "end": end}) + if not form.is_valid(): + return Response(form.errors, status=400) + + day = form.cleaned_data["day"] + begin_hour = form.cleaned_data["begin"] + end_hour = form.cleaned_data["end"] + + begin = timezone.make_aware(datetime.datetime.combine(day, begin_hour)) + end = timezone.make_aware(datetime.datetime.combine(day, end_hour)) + + rooms = Room.objects.qsjps(begin, end) + rooms_json = RoomSerializer(rooms, many=True) + return Response(rooms_json.data) + + +class CourseViewSet(viewsets.ReadOnlyModelViewSet): + queryset = Course.objects.all().prefetch_related("groups", "rooms") + serializer_class = CourseSerializer |