We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Export Reward Point Summary
A new button `Export reward points` should be added to the right of the text showing the number of available reward points on the staff reward points redemption events page. Clicking the button should download a CSV file containing a summary of the reward points.

This file should contain the two columns `Email` and `Points` for each user, listing the number of points currently available for that user (grantings minus redemptions) next to the user's email address. A line should only be added for users where this number of available points is not zero.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `evap/rewards/views.py`
Content:
```
1 from datetime import datetime
2 
3 from django.contrib import messages
4 from django.contrib.messages.views import SuccessMessageMixin
5 from django.core.exceptions import BadRequest, SuspiciousOperation
6 from django.db.models import Sum
7 from django.http import HttpResponse
8 from django.shortcuts import get_object_or_404, redirect, render
9 from django.urls import reverse_lazy
10 from django.utils.translation import get_language
11 from django.utils.translation import gettext as _
12 from django.utils.translation import gettext_lazy
13 from django.views.decorators.http import require_POST
14 from django.views.generic import CreateView, UpdateView
15 
16 from evap.evaluation.auth import manager_required, reward_user_required
17 from evap.evaluation.models import Semester
18 from evap.evaluation.tools import AttachmentResponse, get_object_from_dict_pk_entry_or_logged_40x
19 from evap.rewards.exporters import RewardsExporter
20 from evap.rewards.forms import RewardPointRedemptionEventForm
21 from evap.rewards.models import (
22     NoPointsSelectedError,
23     NotEnoughPointsError,
24     OutdatedRedemptionDataError,
25     RedemptionEventExpiredError,
26     RewardPointGranting,
27     RewardPointRedemption,
28     RewardPointRedemptionEvent,
29     SemesterActivation,
30 )
31 from evap.rewards.tools import grant_eligible_reward_points_for_semester, reward_points_of_user, save_redemptions
32 
33 
34 def redeem_reward_points(request):
35     redemptions = {}
36     try:
37         for key, value in request.POST.items():
38             if key.startswith("points-"):
39                 event_id = int(key.rpartition("-")[2])
40                 redemptions[event_id] = int(value)
41         previous_redeemed_points = int(request.POST["previous_redeemed_points"])
42     except (ValueError, KeyError, TypeError) as e:
43         raise BadRequest from e
44 
45     try:
46         save_redemptions(request, redemptions, previous_redeemed_points)
47         messages.success(request, _("You successfully redeemed your points."))
48     except (
49         NoPointsSelectedError,
50         NotEnoughPointsError,
51         RedemptionEventExpiredError,
52         OutdatedRedemptionDataError,
53     ) as error:
54         status_code = 400
55         if isinstance(error, NoPointsSelectedError):
56             error_string = _("You cannot redeem 0 points.")
57         elif isinstance(error, NotEnoughPointsError):
58             error_string = _("You don't have enough reward points.")
59         elif isinstance(error, RedemptionEventExpiredError):
60             error_string = _("Sorry, the deadline for this event expired already.")
61         elif isinstance(error, OutdatedRedemptionDataError):
62             status_code = 409
63             error_string = _(
64                 "It appears that your browser sent multiple redemption requests. You can see all successful redemptions below."
65             )
66         messages.error(request, error_string)
67         return status_code
68     return 200
69 
70 
71 @reward_user_required
72 def index(request):
73     status = 200
74     if request.method == "POST":
75         status = redeem_reward_points(request)
76     total_points_available = reward_points_of_user(request.user)
77     reward_point_grantings = RewardPointGranting.objects.filter(user_profile=request.user)
78     reward_point_redemptions = RewardPointRedemption.objects.filter(user_profile=request.user)
79     events = RewardPointRedemptionEvent.objects.filter(redeem_end_date__gte=datetime.now()).order_by("date")
80 
81     granted_point_actions = [
82         (granting.granting_time, _("Reward for") + " " + granting.semester.name, granting.value, "")
83         for granting in reward_point_grantings
84     ]
85     redemption_point_actions = [
86         (redemption.redemption_time, redemption.event.name, "", redemption.value)
87         for redemption in reward_point_redemptions
88     ]
89 
90     reward_point_actions = sorted(
91         granted_point_actions + redemption_point_actions, key=lambda action: action[0], reverse=True
92     )
93 
94     template_data = {
95         "reward_point_actions": reward_point_actions,
96         "total_points_available": total_points_available,
97         "total_points_spent": sum(redemption.value for redemption in reward_point_redemptions),
98         "events": events,
99     }
100     return render(request, "rewards_index.html", template_data, status=status)
101 
102 
103 @manager_required
104 def reward_point_redemption_events(request):
105     upcoming_events = RewardPointRedemptionEvent.objects.filter(redeem_end_date__gte=datetime.now()).order_by("date")
106     past_events = RewardPointRedemptionEvent.objects.filter(redeem_end_date__lt=datetime.now()).order_by("-date")
107     total_points_granted = RewardPointGranting.objects.aggregate(Sum("value"))["value__sum"] or 0
108     total_points_redeemed = RewardPointRedemption.objects.aggregate(Sum("value"))["value__sum"] or 0
109     total_points_available = total_points_granted - total_points_redeemed
110     template_data = {
111         "upcoming_events": upcoming_events,
112         "past_events": past_events,
113         "total_points_available": total_points_available,
114     }
115     return render(request, "rewards_reward_point_redemption_events.html", template_data)
116 
117 
118 @manager_required
119 class RewardPointRedemptionEventCreateView(SuccessMessageMixin, CreateView):
120     model = RewardPointRedemptionEvent
121     form_class = RewardPointRedemptionEventForm
122     template_name = "rewards_reward_point_redemption_event_form.html"
123     success_url = reverse_lazy("rewards:reward_point_redemption_events")
124     success_message = gettext_lazy("Successfully created event.")
125 
126 
127 @manager_required
128 class RewardPointRedemptionEventEditView(SuccessMessageMixin, UpdateView):
129     model = RewardPointRedemptionEvent
130     form_class = RewardPointRedemptionEventForm
131     template_name = "rewards_reward_point_redemption_event_form.html"
132     success_url = reverse_lazy("rewards:reward_point_redemption_events")
133     success_message = gettext_lazy("Successfully updated event.")
134     pk_url_kwarg = "event_id"
135     context_object_name = "event"
136 
137 
138 @require_POST
139 @manager_required
140 def reward_point_redemption_event_delete(request):
141     event = get_object_from_dict_pk_entry_or_logged_40x(RewardPointRedemptionEvent, request.POST, "event_id")
142 
143     if not event.can_delete:
144         raise SuspiciousOperation("Deleting redemption event not allowed")
145     event.delete()
146     return HttpResponse()  # 200 OK
147 
148 
149 @manager_required
150 def reward_point_redemption_event_export(request, event_id):
151     event = get_object_or_404(RewardPointRedemptionEvent, id=event_id)
152 
153     filename = _("RewardPoints") + f"-{event.date}-{event.name}-{get_language()}.xls"
154     response = AttachmentResponse(filename, content_type="application/vnd.ms-excel")
155 
156     RewardsExporter().export(response, event.redemptions_by_user())
157 
158     return response
159 
160 
161 @require_POST
162 @manager_required
163 def semester_activation_edit(request, semester_id):
164     semester = get_object_or_404(Semester, id=semester_id)
165     status = request.POST.get("activation_status")
166     if status == "on":
167         active = True
168     elif status == "off":
169         active = False
170     else:
171         raise SuspiciousOperation("Invalid activation keyword")
172     SemesterActivation.objects.update_or_create(semester=semester, defaults={"is_active": active})
173     if active:
174         grant_eligible_reward_points_for_semester(request, semester)
175     return redirect("staff:semester_view", semester_id)
176
```

Path: `evap/rewards/urls.py`
Content:
```
1 from django.urls import path
2 
3 from evap.rewards import views
4 
5 app_name = "rewards"
6 
7 urlpatterns = [
8     path("", views.index, name="index"),
9 
10     path("reward_point_redemption_events/", views.reward_point_redemption_events, name="reward_point_redemption_events"),
11     path("reward_point_redemption_event/create", views.RewardPointRedemptionEventCreateView.as_view(), name="reward_point_redemption_event_create"),
12     path("reward_point_redemption_event/<int:event_id>/edit", views.RewardPointRedemptionEventEditView.as_view(), name="reward_point_redemption_event_edit"),
13     path("reward_point_redemption_event/<int:event_id>/export", views.reward_point_redemption_event_export, name="reward_point_redemption_event_export"),
14     path("reward_point_redemption_event/delete", views.reward_point_redemption_event_delete, name="reward_point_redemption_event_delete"),
15 
16     path("semester_activation/<int:semester_id>/edit", views.semester_activation_edit, name="semester_activation_edit"),
17 ]
18
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/evap/rewards/urls.py b/evap/rewards/urls.py
--- a/evap/rewards/urls.py
+++ b/evap/rewards/urls.py
@@ -7,6 +7,7 @@
 urlpatterns = [
     path("", views.index, name="index"),
 
+    path("reward_points_export", views.reward_points_export, name="reward_points_export"),
     path("reward_point_redemption_events/", views.reward_point_redemption_events, name="reward_point_redemption_events"),
     path("reward_point_redemption_event/create", views.RewardPointRedemptionEventCreateView.as_view(), name="reward_point_redemption_event_create"),
     path("reward_point_redemption_event/<int:event_id>/edit", views.RewardPointRedemptionEventEditView.as_view(), name="reward_point_redemption_event_edit"),
diff --git a/evap/rewards/views.py b/evap/rewards/views.py
--- a/evap/rewards/views.py
+++ b/evap/rewards/views.py
@@ -1,3 +1,4 @@
+import csv
 from datetime import datetime
 
 from django.contrib import messages
@@ -14,7 +15,7 @@
 from django.views.generic import CreateView, UpdateView
 
 from evap.evaluation.auth import manager_required, reward_user_required
-from evap.evaluation.models import Semester
+from evap.evaluation.models import Semester, UserProfile
 from evap.evaluation.tools import AttachmentResponse, get_object_from_dict_pk_entry_or_logged_40x
 from evap.rewards.exporters import RewardsExporter
 from evap.rewards.forms import RewardPointRedemptionEventForm
@@ -158,6 +159,32 @@
     return response
 
 
+@manager_required
+def reward_points_export(request):
+    filename = _("RewardPoints") + f"-{get_language()}.csv"
+    response = AttachmentResponse(filename, content_type="text/csv")
+
+    writer = csv.writer(response, delimiter=";", lineterminator="\n")
+    writer.writerow([_("Email address"), _("Number of points")])
+    profiles_with_points = (
+        UserProfile.objects.annotate(
+            points=Sum("reward_point_grantings__value", default=0) - Sum("reward_point_redemptions__value", default=0)
+        )
+        .filter(points__gt=0)
+        .order_by("-points")
+    )
+
+    for profile in profiles_with_points.all():
+        writer.writerow(
+            [
+                profile.email,
+                profile.points,
+            ]
+        )
+
+    return response
+
+
 @require_POST
 @manager_required
 def semester_activation_edit(request, semester_id):

{"golden_diff": "diff --git a/evap/rewards/urls.py b/evap/rewards/urls.py\n--- a/evap/rewards/urls.py\n+++ b/evap/rewards/urls.py\n@@ -7,6 +7,7 @@\n urlpatterns = [\n     path(\"\", views.index, name=\"index\"),\n \n+    path(\"reward_points_export\", views.reward_points_export, name=\"reward_points_export\"),\n     path(\"reward_point_redemption_events/\", views.reward_point_redemption_events, name=\"reward_point_redemption_events\"),\n     path(\"reward_point_redemption_event/create\", views.RewardPointRedemptionEventCreateView.as_view(), name=\"reward_point_redemption_event_create\"),\n     path(\"reward_point_redemption_event/<int:event_id>/edit\", views.RewardPointRedemptionEventEditView.as_view(), name=\"reward_point_redemption_event_edit\"),\ndiff --git a/evap/rewards/views.py b/evap/rewards/views.py\n--- a/evap/rewards/views.py\n+++ b/evap/rewards/views.py\n@@ -1,3 +1,4 @@\n+import csv\n from datetime import datetime\n \n from django.contrib import messages\n@@ -14,7 +15,7 @@\n from django.views.generic import CreateView, UpdateView\n \n from evap.evaluation.auth import manager_required, reward_user_required\n-from evap.evaluation.models import Semester\n+from evap.evaluation.models import Semester, UserProfile\n from evap.evaluation.tools import AttachmentResponse, get_object_from_dict_pk_entry_or_logged_40x\n from evap.rewards.exporters import RewardsExporter\n from evap.rewards.forms import RewardPointRedemptionEventForm\n@@ -158,6 +159,32 @@\n     return response\n \n \n+@manager_required\n+def reward_points_export(request):\n+    filename = _(\"RewardPoints\") + f\"-{get_language()}.csv\"\n+    response = AttachmentResponse(filename, content_type=\"text/csv\")\n+\n+    writer = csv.writer(response, delimiter=\";\", lineterminator=\"\\n\")\n+    writer.writerow([_(\"Email address\"), _(\"Number of points\")])\n+    profiles_with_points = (\n+        UserProfile.objects.annotate(\n+            points=Sum(\"reward_point_grantings__value\", default=0) - Sum(\"reward_point_redemptions__value\", default=0)\n+        )\n+        .filter(points__gt=0)\n+        .order_by(\"-points\")\n+    )\n+\n+    for profile in profiles_with_points.all():\n+        writer.writerow(\n+            [\n+                profile.email,\n+                profile.points,\n+            ]\n+        )\n+\n+    return response\n+\n+\n @require_POST\n @manager_required\n def semester_activation_edit(request, semester_id):\n", "issue": "Export Reward Point Summary\nA new button `Export reward points` should be added to the right of the text showing the number of available reward points on the staff reward points redemption events page. Clicking the button should download a CSV file containing a summary of the reward points.\r\n\r\nThis file should contain the two columns `Email` and `Points` for each user, listing the number of points currently available for that user (grantings minus redemptions) next to the user's email address. A line should only be added for users where this number of available points is not zero.\n", "before_files": [{"content": "from datetime import datetime\n\nfrom django.contrib import messages\nfrom django.contrib.messages.views import SuccessMessageMixin\nfrom django.core.exceptions import BadRequest, SuspiciousOperation\nfrom django.db.models import Sum\nfrom django.http import HttpResponse\nfrom django.shortcuts import get_object_or_404, redirect, render\nfrom django.urls import reverse_lazy\nfrom django.utils.translation import get_language\nfrom django.utils.translation import gettext as _\nfrom django.utils.translation import gettext_lazy\nfrom django.views.decorators.http import require_POST\nfrom django.views.generic import CreateView, UpdateView\n\nfrom evap.evaluation.auth import manager_required, reward_user_required\nfrom evap.evaluation.models import Semester\nfrom evap.evaluation.tools import AttachmentResponse, get_object_from_dict_pk_entry_or_logged_40x\nfrom evap.rewards.exporters import RewardsExporter\nfrom evap.rewards.forms import RewardPointRedemptionEventForm\nfrom evap.rewards.models import (\n    NoPointsSelectedError,\n    NotEnoughPointsError,\n    OutdatedRedemptionDataError,\n    RedemptionEventExpiredError,\n    RewardPointGranting,\n    RewardPointRedemption,\n    RewardPointRedemptionEvent,\n    SemesterActivation,\n)\nfrom evap.rewards.tools import grant_eligible_reward_points_for_semester, reward_points_of_user, save_redemptions\n\n\ndef redeem_reward_points(request):\n    redemptions = {}\n    try:\n        for key, value in request.POST.items():\n            if key.startswith(\"points-\"):\n                event_id = int(key.rpartition(\"-\")[2])\n                redemptions[event_id] = int(value)\n        previous_redeemed_points = int(request.POST[\"previous_redeemed_points\"])\n    except (ValueError, KeyError, TypeError) as e:\n        raise BadRequest from e\n\n    try:\n        save_redemptions(request, redemptions, previous_redeemed_points)\n        messages.success(request, _(\"You successfully redeemed your points.\"))\n    except (\n        NoPointsSelectedError,\n        NotEnoughPointsError,\n        RedemptionEventExpiredError,\n        OutdatedRedemptionDataError,\n    ) as error:\n        status_code = 400\n        if isinstance(error, NoPointsSelectedError):\n            error_string = _(\"You cannot redeem 0 points.\")\n        elif isinstance(error, NotEnoughPointsError):\n            error_string = _(\"You don't have enough reward points.\")\n        elif isinstance(error, RedemptionEventExpiredError):\n            error_string = _(\"Sorry, the deadline for this event expired already.\")\n        elif isinstance(error, OutdatedRedemptionDataError):\n            status_code = 409\n            error_string = _(\n                \"It appears that your browser sent multiple redemption requests. You can see all successful redemptions below.\"\n            )\n        messages.error(request, error_string)\n        return status_code\n    return 200\n\n\n@reward_user_required\ndef index(request):\n    status = 200\n    if request.method == \"POST\":\n        status = redeem_reward_points(request)\n    total_points_available = reward_points_of_user(request.user)\n    reward_point_grantings = RewardPointGranting.objects.filter(user_profile=request.user)\n    reward_point_redemptions = RewardPointRedemption.objects.filter(user_profile=request.user)\n    events = RewardPointRedemptionEvent.objects.filter(redeem_end_date__gte=datetime.now()).order_by(\"date\")\n\n    granted_point_actions = [\n        (granting.granting_time, _(\"Reward for\") + \" \" + granting.semester.name, granting.value, \"\")\n        for granting in reward_point_grantings\n    ]\n    redemption_point_actions = [\n        (redemption.redemption_time, redemption.event.name, \"\", redemption.value)\n        for redemption in reward_point_redemptions\n    ]\n\n    reward_point_actions = sorted(\n        granted_point_actions + redemption_point_actions, key=lambda action: action[0], reverse=True\n    )\n\n    template_data = {\n        \"reward_point_actions\": reward_point_actions,\n        \"total_points_available\": total_points_available,\n        \"total_points_spent\": sum(redemption.value for redemption in reward_point_redemptions),\n        \"events\": events,\n    }\n    return render(request, \"rewards_index.html\", template_data, status=status)\n\n\n@manager_required\ndef reward_point_redemption_events(request):\n    upcoming_events = RewardPointRedemptionEvent.objects.filter(redeem_end_date__gte=datetime.now()).order_by(\"date\")\n    past_events = RewardPointRedemptionEvent.objects.filter(redeem_end_date__lt=datetime.now()).order_by(\"-date\")\n    total_points_granted = RewardPointGranting.objects.aggregate(Sum(\"value\"))[\"value__sum\"] or 0\n    total_points_redeemed = RewardPointRedemption.objects.aggregate(Sum(\"value\"))[\"value__sum\"] or 0\n    total_points_available = total_points_granted - total_points_redeemed\n    template_data = {\n        \"upcoming_events\": upcoming_events,\n        \"past_events\": past_events,\n        \"total_points_available\": total_points_available,\n    }\n    return render(request, \"rewards_reward_point_redemption_events.html\", template_data)\n\n\n@manager_required\nclass RewardPointRedemptionEventCreateView(SuccessMessageMixin, CreateView):\n    model = RewardPointRedemptionEvent\n    form_class = RewardPointRedemptionEventForm\n    template_name = \"rewards_reward_point_redemption_event_form.html\"\n    success_url = reverse_lazy(\"rewards:reward_point_redemption_events\")\n    success_message = gettext_lazy(\"Successfully created event.\")\n\n\n@manager_required\nclass RewardPointRedemptionEventEditView(SuccessMessageMixin, UpdateView):\n    model = RewardPointRedemptionEvent\n    form_class = RewardPointRedemptionEventForm\n    template_name = \"rewards_reward_point_redemption_event_form.html\"\n    success_url = reverse_lazy(\"rewards:reward_point_redemption_events\")\n    success_message = gettext_lazy(\"Successfully updated event.\")\n    pk_url_kwarg = \"event_id\"\n    context_object_name = \"event\"\n\n\n@require_POST\n@manager_required\ndef reward_point_redemption_event_delete(request):\n    event = get_object_from_dict_pk_entry_or_logged_40x(RewardPointRedemptionEvent, request.POST, \"event_id\")\n\n    if not event.can_delete:\n        raise SuspiciousOperation(\"Deleting redemption event not allowed\")\n    event.delete()\n    return HttpResponse()  # 200 OK\n\n\n@manager_required\ndef reward_point_redemption_event_export(request, event_id):\n    event = get_object_or_404(RewardPointRedemptionEvent, id=event_id)\n\n    filename = _(\"RewardPoints\") + f\"-{event.date}-{event.name}-{get_language()}.xls\"\n    response = AttachmentResponse(filename, content_type=\"application/vnd.ms-excel\")\n\n    RewardsExporter().export(response, event.redemptions_by_user())\n\n    return response\n\n\n@require_POST\n@manager_required\ndef semester_activation_edit(request, semester_id):\n    semester = get_object_or_404(Semester, id=semester_id)\n    status = request.POST.get(\"activation_status\")\n    if status == \"on\":\n        active = True\n    elif status == \"off\":\n        active = False\n    else:\n        raise SuspiciousOperation(\"Invalid activation keyword\")\n    SemesterActivation.objects.update_or_create(semester=semester, defaults={\"is_active\": active})\n    if active:\n        grant_eligible_reward_points_for_semester(request, semester)\n    return redirect(\"staff:semester_view\", semester_id)\n", "path": "evap/rewards/views.py"}, {"content": "from django.urls import path\n\nfrom evap.rewards import views\n\napp_name = \"rewards\"\n\nurlpatterns = [\n    path(\"\", views.index, name=\"index\"),\n\n    path(\"reward_point_redemption_events/\", views.reward_point_redemption_events, name=\"reward_point_redemption_events\"),\n    path(\"reward_point_redemption_event/create\", views.RewardPointRedemptionEventCreateView.as_view(), name=\"reward_point_redemption_event_create\"),\n    path(\"reward_point_redemption_event/<int:event_id>/edit\", views.RewardPointRedemptionEventEditView.as_view(), name=\"reward_point_redemption_event_edit\"),\n    path(\"reward_point_redemption_event/<int:event_id>/export\", views.reward_point_redemption_event_export, name=\"reward_point_redemption_event_export\"),\n    path(\"reward_point_redemption_event/delete\", views.reward_point_redemption_event_delete, name=\"reward_point_redemption_event_delete\"),\n\n    path(\"semester_activation/<int:semester_id>/edit\", views.semester_activation_edit, name=\"semester_activation_edit\"),\n]\n", "path": "evap/rewards/urls.py"}], "after_files": [{"content": "import csv\nfrom datetime import datetime\n\nfrom django.contrib import messages\nfrom django.contrib.messages.views import SuccessMessageMixin\nfrom django.core.exceptions import BadRequest, SuspiciousOperation\nfrom django.db.models import Sum\nfrom django.http import HttpResponse\nfrom django.shortcuts import get_object_or_404, redirect, render\nfrom django.urls import reverse_lazy\nfrom django.utils.translation import get_language\nfrom django.utils.translation import gettext as _\nfrom django.utils.translation import gettext_lazy\nfrom django.views.decorators.http import require_POST\nfrom django.views.generic import CreateView, UpdateView\n\nfrom evap.evaluation.auth import manager_required, reward_user_required\nfrom evap.evaluation.models import Semester, UserProfile\nfrom evap.evaluation.tools import AttachmentResponse, get_object_from_dict_pk_entry_or_logged_40x\nfrom evap.rewards.exporters import RewardsExporter\nfrom evap.rewards.forms import RewardPointRedemptionEventForm\nfrom evap.rewards.models import (\n    NoPointsSelectedError,\n    NotEnoughPointsError,\n    OutdatedRedemptionDataError,\n    RedemptionEventExpiredError,\n    RewardPointGranting,\n    RewardPointRedemption,\n    RewardPointRedemptionEvent,\n    SemesterActivation,\n)\nfrom evap.rewards.tools import grant_eligible_reward_points_for_semester, reward_points_of_user, save_redemptions\n\n\ndef redeem_reward_points(request):\n    redemptions = {}\n    try:\n        for key, value in request.POST.items():\n            if key.startswith(\"points-\"):\n                event_id = int(key.rpartition(\"-\")[2])\n                redemptions[event_id] = int(value)\n        previous_redeemed_points = int(request.POST[\"previous_redeemed_points\"])\n    except (ValueError, KeyError, TypeError) as e:\n        raise BadRequest from e\n\n    try:\n        save_redemptions(request, redemptions, previous_redeemed_points)\n        messages.success(request, _(\"You successfully redeemed your points.\"))\n    except (\n        NoPointsSelectedError,\n        NotEnoughPointsError,\n        RedemptionEventExpiredError,\n        OutdatedRedemptionDataError,\n    ) as error:\n        status_code = 400\n        if isinstance(error, NoPointsSelectedError):\n            error_string = _(\"You cannot redeem 0 points.\")\n        elif isinstance(error, NotEnoughPointsError):\n            error_string = _(\"You don't have enough reward points.\")\n        elif isinstance(error, RedemptionEventExpiredError):\n            error_string = _(\"Sorry, the deadline for this event expired already.\")\n        elif isinstance(error, OutdatedRedemptionDataError):\n            status_code = 409\n            error_string = _(\n                \"It appears that your browser sent multiple redemption requests. You can see all successful redemptions below.\"\n            )\n        messages.error(request, error_string)\n        return status_code\n    return 200\n\n\n@reward_user_required\ndef index(request):\n    status = 200\n    if request.method == \"POST\":\n        status = redeem_reward_points(request)\n    total_points_available = reward_points_of_user(request.user)\n    reward_point_grantings = RewardPointGranting.objects.filter(user_profile=request.user)\n    reward_point_redemptions = RewardPointRedemption.objects.filter(user_profile=request.user)\n    events = RewardPointRedemptionEvent.objects.filter(redeem_end_date__gte=datetime.now()).order_by(\"date\")\n\n    granted_point_actions = [\n        (granting.granting_time, _(\"Reward for\") + \" \" + granting.semester.name, granting.value, \"\")\n        for granting in reward_point_grantings\n    ]\n    redemption_point_actions = [\n        (redemption.redemption_time, redemption.event.name, \"\", redemption.value)\n        for redemption in reward_point_redemptions\n    ]\n\n    reward_point_actions = sorted(\n        granted_point_actions + redemption_point_actions, key=lambda action: action[0], reverse=True\n    )\n\n    template_data = {\n        \"reward_point_actions\": reward_point_actions,\n        \"total_points_available\": total_points_available,\n        \"total_points_spent\": sum(redemption.value for redemption in reward_point_redemptions),\n        \"events\": events,\n    }\n    return render(request, \"rewards_index.html\", template_data, status=status)\n\n\n@manager_required\ndef reward_point_redemption_events(request):\n    upcoming_events = RewardPointRedemptionEvent.objects.filter(redeem_end_date__gte=datetime.now()).order_by(\"date\")\n    past_events = RewardPointRedemptionEvent.objects.filter(redeem_end_date__lt=datetime.now()).order_by(\"-date\")\n    total_points_granted = RewardPointGranting.objects.aggregate(Sum(\"value\"))[\"value__sum\"] or 0\n    total_points_redeemed = RewardPointRedemption.objects.aggregate(Sum(\"value\"))[\"value__sum\"] or 0\n    total_points_available = total_points_granted - total_points_redeemed\n    template_data = {\n        \"upcoming_events\": upcoming_events,\n        \"past_events\": past_events,\n        \"total_points_available\": total_points_available,\n    }\n    return render(request, \"rewards_reward_point_redemption_events.html\", template_data)\n\n\n@manager_required\nclass RewardPointRedemptionEventCreateView(SuccessMessageMixin, CreateView):\n    model = RewardPointRedemptionEvent\n    form_class = RewardPointRedemptionEventForm\n    template_name = \"rewards_reward_point_redemption_event_form.html\"\n    success_url = reverse_lazy(\"rewards:reward_point_redemption_events\")\n    success_message = gettext_lazy(\"Successfully created event.\")\n\n\n@manager_required\nclass RewardPointRedemptionEventEditView(SuccessMessageMixin, UpdateView):\n    model = RewardPointRedemptionEvent\n    form_class = RewardPointRedemptionEventForm\n    template_name = \"rewards_reward_point_redemption_event_form.html\"\n    success_url = reverse_lazy(\"rewards:reward_point_redemption_events\")\n    success_message = gettext_lazy(\"Successfully updated event.\")\n    pk_url_kwarg = \"event_id\"\n    context_object_name = \"event\"\n\n\n@require_POST\n@manager_required\ndef reward_point_redemption_event_delete(request):\n    event = get_object_from_dict_pk_entry_or_logged_40x(RewardPointRedemptionEvent, request.POST, \"event_id\")\n\n    if not event.can_delete:\n        raise SuspiciousOperation(\"Deleting redemption event not allowed\")\n    event.delete()\n    return HttpResponse()  # 200 OK\n\n\n@manager_required\ndef reward_point_redemption_event_export(request, event_id):\n    event = get_object_or_404(RewardPointRedemptionEvent, id=event_id)\n\n    filename = _(\"RewardPoints\") + f\"-{event.date}-{event.name}-{get_language()}.xls\"\n    response = AttachmentResponse(filename, content_type=\"application/vnd.ms-excel\")\n\n    RewardsExporter().export(response, event.redemptions_by_user())\n\n    return response\n\n\n@manager_required\ndef reward_points_export(request):\n    filename = _(\"RewardPoints\") + f\"-{get_language()}.csv\"\n    response = AttachmentResponse(filename, content_type=\"text/csv\")\n\n    writer = csv.writer(response, delimiter=\";\", lineterminator=\"\\n\")\n    writer.writerow([_(\"Email address\"), _(\"Number of points\")])\n    profiles_with_points = (\n        UserProfile.objects.annotate(\n            points=Sum(\"reward_point_grantings__value\", default=0) - Sum(\"reward_point_redemptions__value\", default=0)\n        )\n        .filter(points__gt=0)\n        .order_by(\"-points\")\n    )\n\n    for profile in profiles_with_points.all():\n        writer.writerow(\n            [\n                profile.email,\n                profile.points,\n            ]\n        )\n\n    return response\n\n\n@require_POST\n@manager_required\ndef semester_activation_edit(request, semester_id):\n    semester = get_object_or_404(Semester, id=semester_id)\n    status = request.POST.get(\"activation_status\")\n    if status == \"on\":\n        active = True\n    elif status == \"off\":\n        active = False\n    else:\n        raise SuspiciousOperation(\"Invalid activation keyword\")\n    SemesterActivation.objects.update_or_create(semester=semester, defaults={\"is_active\": active})\n    if active:\n        grant_eligible_reward_points_for_semester(request, semester)\n    return redirect(\"staff:semester_view\", semester_id)\n", "path": "evap/rewards/views.py"}, {"content": "from django.urls import path\n\nfrom evap.rewards import views\n\napp_name = \"rewards\"\n\nurlpatterns = [\n    path(\"\", views.index, name=\"index\"),\n\n    path(\"reward_points_export\", views.reward_points_export, name=\"reward_points_export\"),\n    path(\"reward_point_redemption_events/\", views.reward_point_redemption_events, name=\"reward_point_redemption_events\"),\n    path(\"reward_point_redemption_event/create\", views.RewardPointRedemptionEventCreateView.as_view(), name=\"reward_point_redemption_event_create\"),\n    path(\"reward_point_redemption_event/<int:event_id>/edit\", views.RewardPointRedemptionEventEditView.as_view(), name=\"reward_point_redemption_event_edit\"),\n    path(\"reward_point_redemption_event/<int:event_id>/export\", views.reward_point_redemption_event_export, name=\"reward_point_redemption_event_export\"),\n    path(\"reward_point_redemption_event/delete\", views.reward_point_redemption_event_delete, name=\"reward_point_redemption_event_delete\"),\n\n    path(\"semester_activation/<int:semester_id>/edit\", views.semester_activation_edit, name=\"semester_activation_edit\"),\n]\n", "path": "evap/rewards/urls.py"}]}