views.py 7.83 KB
Newer Older
1
"""Views provided by the education package"""
2
import os
3
from datetime import datetime, date
Jan's avatar
Jan committed
4

5
from django.contrib import messages
Jan's avatar
Jan committed
6
from django.contrib.auth.decorators import login_required
7
from django.contrib.messages.views import SuccessMessageMixin
8
from django.core.exceptions import PermissionDenied
9
10
from django.http import HttpResponseRedirect, HttpResponse
from django.urls import reverse_lazy
11
from django.utils import timezone
12
from django.utils.decorators import method_decorator
13
from django.utils.translation import gettext_lazy as _, get_language
14
from django.views.generic import ListView, DetailView, CreateView, TemplateView
15
from django_sendfile import sendfile
Jan's avatar
Jan committed
16

17
from members.decorators import membership_required
Jan's avatar
Jan committed
18
19
20
21
from .forms import AddExamForm, AddSummaryForm
from .models import Category, Course, Exam, Summary


22
class CourseIndexView(ListView):
23
24
    """
    Renders an overview of the courses
25
    """
Luko van der Maas's avatar
Luko van der Maas committed
26

27
    queryset = Course.objects.filter(until=None)
Luko van der Maas's avatar
Luko van der Maas committed
28
    template_name = "education/courses.html"
29
30

    def get_ordering(self) -> str:
Luko van der Maas's avatar
Luko van der Maas committed
31
        return f"name_{get_language()}"
32
33
34

    def get_context_data(self, **kwargs) -> dict:
        context = super().get_context_data(**kwargs)
Luko van der Maas's avatar
Luko van der Maas committed
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
        context.update(
            {
                "courses": (
                    {
                        "course_code": x.course_code,
                        "name": x.name,
                        "categories": x.categories.all(),
                        "document_count": sum(
                            [
                                x.summary_set.filter(accepted=True).count(),
                                x.exam_set.filter(accepted=True).count(),
                            ]
                            + [
                                c.summary_set.filter(accepted=True).count()
                                + c.exam_set.filter(accepted=True).count()
                                for c in x.old_courses.all()
                            ]
                        ),
                        "url": x.get_absolute_url(),
                    }
                    for x in context["object_list"]
                ),
                "categories": Category.objects.all(),
            }
        )
60
61
62
63
        return context


class CourseDetailView(DetailView):
64
65
    """
    Renders the detail page of one specific course
66
    """
Luko van der Maas's avatar
Luko van der Maas committed
67

68
    model = Course
Luko van der Maas's avatar
Luko van der Maas committed
69
70
    context_object_name = "course"
    template_name = "education/course.html"
71
72
73

    def get_context_data(self, **kwargs) -> dict:
        context = super().get_context_data(**kwargs)
Luko van der Maas's avatar
Luko van der Maas committed
74
        obj = context["course"]
75
76
77
78
79
80
81
        courses = list(obj.old_courses.all())
        courses.append(obj)
        items = {}
        for course in courses:
            for summary in course.summary_set.filter(accepted=True):
                if summary.year not in items:
                    items[summary.year] = {
Luko van der Maas's avatar
Luko van der Maas committed
82
83
84
                        "summaries": [],
                        "exams": [],
                        "legacy": course if course.pk != obj.pk else None,
85
                    }
Luko van der Maas's avatar
Luko van der Maas committed
86
87
88
89
90
91
92
93
                items[summary.year]["summaries"].append(
                    {
                        "year": summary.year,
                        "name": f'{_("Summary")} {summary.name}',
                        "language": summary.language,
                        "id": summary.id,
                    }
                )
94
95
96
            for exam in course.exam_set.filter(accepted=True):
                if exam.year not in items:
                    items[exam.year] = {
Luko van der Maas's avatar
Luko van der Maas committed
97
98
99
100
101
102
103
104
105
106
107
                        "summaries": [],
                        "exams": [],
                        "legacy": course if course.pk != obj.pk else None,
                    }
                items[exam.year]["exams"].append(
                    {
                        "type": "exam",
                        "year": exam.year,
                        "name": f"{exam.get_type_display()} {exam.name}",
                        "language": exam.language,
                        "id": exam.id,
108
                    }
Luko van der Maas's avatar
Luko van der Maas committed
109
110
                )
        context.update({"items": sorted(items.items(), key=lambda x: x[0])})
111
112
113
        return context


Luko van der Maas's avatar
Luko van der Maas committed
114
115
@method_decorator(login_required, "dispatch")
@method_decorator(membership_required, "dispatch")
116
class ExamDetailView(DetailView):
117
118
119
    """
    Fetches and outputs the specified exam
    """
Luko van der Maas's avatar
Luko van der Maas committed
120

121
    model = Exam
122

123
124
    def get(self, request, *args, **kwargs) -> HttpResponse:
        response = super().get(request, *args, **kwargs)
Luko van der Maas's avatar
Luko van der Maas committed
125
        exam = response.context_data["object"]
126
127
        exam.download_count += 1
        exam.save()
128

129
        ext = os.path.splitext(exam.file.path)[1]
Luko van der Maas's avatar
Luko van der Maas committed
130
131
132
133
        filename = f"{exam.course.name}-exam{exam.year}{ext}"
        return sendfile(
            request, exam.file.path, attachment=True, attachment_filename=filename
        )
Jan's avatar
Jan committed
134
135


Luko van der Maas's avatar
Luko van der Maas committed
136
137
@method_decorator(login_required, "dispatch")
@method_decorator(membership_required, "dispatch")
138
class SummaryDetailView(DetailView):
139
140
141
    """
    Fetches and outputs the specified summary
    """
Luko van der Maas's avatar
Luko van der Maas committed
142

143
    model = Summary
144

145
146
    def get(self, request, *args, **kwargs) -> HttpResponse:
        response = super().get(request, *args, **kwargs)
Luko van der Maas's avatar
Luko van der Maas committed
147
        obj = response.context_data["object"]
148
149
        obj.download_count += 1
        obj.save()
150

151
        ext = os.path.splitext(obj.file.path)[1]
Luko van der Maas's avatar
Luko van der Maas committed
152
153
154
155
        filename = f"{obj.course.name}-summary{obj.year}{ext}"
        return sendfile(
            request, obj.file.path, attachment=True, attachment_filename=filename
        )
Jan's avatar
Jan committed
156
157


Luko van der Maas's avatar
Luko van der Maas committed
158
159
@method_decorator(login_required, "dispatch")
@method_decorator(membership_required, "dispatch")
160
class ExamCreateView(SuccessMessageMixin, CreateView):
161
162
163
    """
    Renders the form to submit a new exam
    """
Luko van der Maas's avatar
Luko van der Maas committed
164

165
166
    model = Exam
    form_class = AddExamForm
Luko van der Maas's avatar
Luko van der Maas committed
167
168
169
    template_name = "education/add_exam.html"
    success_url = reverse_lazy("education:submit-exam")
    success_message = _("Exam submitted successfully.")
170
171
172

    def get_initial(self) -> dict:
        initial = super().get_initial()
Luko van der Maas's avatar
Luko van der Maas committed
173
174
        initial["exam_date"] = date.today()
        initial["course"] = self.kwargs.get("pk", None)
175
176
177
178
179
180
181
        return initial

    def form_valid(self, form) -> HttpResponse:
        self.object = form.save(commit=False)
        self.object.uploader = self.request.member
        self.object.uploader_date = datetime.now()
        self.object.save()
182
        return super().form_valid(form)
183
184


Luko van der Maas's avatar
Luko van der Maas committed
185
186
@method_decorator(login_required, "dispatch")
@method_decorator(membership_required, "dispatch")
187
class SummaryCreateView(CreateView):
188
189
190
    """
    Renders the form to submit a new summary
    """
Luko van der Maas's avatar
Luko van der Maas committed
191

192
193
    model = Summary
    form_class = AddSummaryForm
Luko van der Maas's avatar
Luko van der Maas committed
194
195
196
    template_name = "education/add_summary.html"
    success_url = reverse_lazy("education:submit-summary")
    success_message = _("Summary submitted successfully.")
197
198
199

    def get_initial(self):
        initial = super().get_initial()
Luko van der Maas's avatar
Luko van der Maas committed
200
201
        initial["author"] = self.request.member.get_full_name()
        initial["course"] = self.kwargs.get("pk", None)
202
203
204
205
206
207
208
        return initial

    def form_valid(self, form) -> HttpResponse:
        self.object = form.save(commit=False)
        self.object.uploader = self.request.member
        self.object.uploader_date = datetime.now()
        self.object.save()
209
        return super().form_valid(form)
210
211


Luko van der Maas's avatar
Luko van der Maas committed
212
@method_decorator(login_required, "dispatch")
213
class BookInfoView(TemplateView):
214
215
216
    """
    Renders a page with information about book sale
    Only available to members and to-be members
217
    """
Luko van der Maas's avatar
Luko van der Maas committed
218
219

    template_name = "education/books.html"
220
221

    def dispatch(self, request, *args, **kwargs) -> HttpResponse:
Luko van der Maas's avatar
Luko van der Maas committed
222
223
224
        if request.member.has_active_membership() or (
            request.member.earliest_membership
            and request.member.earliest_membership.since > timezone.now().date()
225
226
227
        ):
            return super().dispatch(request, *args, **kwargs)
        raise PermissionDenied
228

229
230
231
232

class StudentParticipantView(TemplateView):
    """
    Renders a page with information about student information
233
    """
Luko van der Maas's avatar
Luko van der Maas committed
234
235

    template_name = "education/student_participation.html"