-
Notifications
You must be signed in to change notification settings - Fork 146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Grant reward points on evaluation deletion #2358
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
from django.utils.translation import ngettext | ||
|
||
from evap.evaluation.models import Evaluation, Semester, UserProfile | ||
from evap.evaluation.tools import inside_transaction | ||
from evap.rewards.models import RewardPointGranting, RewardPointRedemption, SemesterActivation | ||
|
||
|
||
|
@@ -100,21 +101,21 @@ | |
|
||
|
||
@receiver(models.signals.m2m_changed, sender=Evaluation.participants.through) | ||
def grant_reward_points_after_delete(instance, action, reverse, pk_set, **_kwargs): | ||
def grant_reward_points_on_participation_change(instance, action, reverse, pk_set, **_kwargs): | ||
# if users do not need to evaluate anymore, they may have earned reward points | ||
if action == "post_remove": | ||
grantings = [] | ||
|
||
if reverse: | ||
# an evaluation got removed from a participant | ||
# one or more evaluations got removed from a participant | ||
user = instance | ||
|
||
for semester in Semester.objects.filter(courses__evaluations__pk__in=pk_set): | ||
granting, __ = grant_reward_points_if_eligible(user, semester) | ||
if granting: | ||
grantings = [granting] | ||
else: | ||
# a participant got removed from an evaluation | ||
# one or more participants got removed from an evaluation | ||
evaluation = instance | ||
|
||
for user in UserProfile.objects.filter(pk__in=pk_set): | ||
|
@@ -123,4 +124,27 @@ | |
grantings.append(granting) | ||
|
||
if grantings: | ||
RewardPointGranting.granted_by_removal.send(sender=RewardPointGranting, grantings=grantings) | ||
RewardPointGranting.granted_by_participation_removal.send(sender=RewardPointGranting, grantings=grantings) | ||
|
||
|
||
@receiver(models.signals.pre_delete, sender=Evaluation) | ||
def grant_reward_points_on_evaluation_delete(instance, **_kwargs): | ||
if not inside_transaction(): | ||
# This will always be true in a django TestCase, so our tests can't meaningfully catch calls that are not | ||
# wrapped in a transaction. Requiring a transaction is a precaution so that an (unlikely) failing .delete() | ||
# execution afterwards doesn't leave us in half-deleted state. Chances are, if deletion fails, staff users | ||
# will still want to delete the instance. | ||
# Currently, only staff:evaluation_delete and staff:semester_delete call .delete() | ||
logger.exception("Called while not inside transaction") | ||
Comment on lines
+132
to
+138
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @niklasmohrin this is all a bit annoying. Ideally, I'd like to
While the signal model allows me to run code before and after deletion, I don't see a nice way to pass the list of participants to it without having global state I think this is fine for now. If we ever run into a "wanted to delete, failed, now need cleanup"-state, we will likely need manual cleanup anyway. I'll look into whether we can ditch the signals and use a custom manager instead, which is what the Django docs propose to do instead:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, it seems a bit fragile; adding a custom manager to the Evaluation model is also a bit annoying because we have rewards logic in the model; maybe we can make our own "register a deletion handler" thing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the docs they say we should only use |
||
|
||
grantings = [] | ||
|
||
participants = list(instance.participants.all()) | ||
instance.participants.clear() | ||
for user in participants: | ||
granting, __ = grant_reward_points_if_eligible(user, instance.course.semester) | ||
if granting: | ||
grantings.append(granting) | ||
|
||
if grantings: | ||
RewardPointGranting.granted_by_evaluation_deletion.send(sender=RewardPointGranting, grantings=grantings) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import csv | ||
import itertools | ||
import logging | ||
from collections import OrderedDict, defaultdict, namedtuple | ||
from collections.abc import Container | ||
from dataclasses import dataclass | ||
|
@@ -135,6 +136,8 @@ | |
from evap.student.views import render_vote_page | ||
from evap.tools import unordered_groupby | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
@manager_required | ||
def index(request): | ||
|
@@ -671,6 +674,8 @@ def semester_make_active(request): | |
@require_POST | ||
@manager_required | ||
def semester_delete(request): | ||
# TODO(rebeling): Do we expect reward point granting here? Do we want to notify? I don't see why it couldn't happen | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @janno42 I didn't find anything in the code that prohibits deleting semesters with active rewards. I think it's possible that we also get a reward point granting here? Do we want that? (If yes, I guess we want a matching There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Semesters can only be deleted when they have no evaluations or all evaluations have been archived. So there can't be any evaluations which should be counted towards reward points, right? |
||
|
||
semester = get_object_from_dict_pk_entry_or_logged_40x(Semester, request.POST, "semester_id") | ||
|
||
if not semester.can_be_deleted_by_manager: | ||
|
@@ -1313,7 +1318,7 @@ def helper_evaluation_edit(request, evaluation): | |
# as the callback is captured by a weak reference in the Django Framework | ||
# and no other strong references are being kept. | ||
# See https://github.com/e-valuation/EvaP/issues/1361 for more information and discussion. | ||
@receiver(RewardPointGranting.granted_by_removal, weak=True) | ||
@receiver(RewardPointGranting.granted_by_participation_removal, weak=True) | ||
def notify_reward_points(grantings, **_kwargs): | ||
for granting in grantings: | ||
messages.info( | ||
|
@@ -1418,9 +1423,15 @@ def helper_single_result_edit(request, evaluation): | |
|
||
@require_POST | ||
@manager_required | ||
@transaction.atomic | ||
def evaluation_delete(request): | ||
evaluation = get_object_from_dict_pk_entry_or_logged_40x(Evaluation, request.POST, "evaluation_id") | ||
|
||
# See comment in helper_evaluation_edit | ||
@receiver(RewardPointGranting.granted_by_evaluation_deletion, weak=True) | ||
def notify_reward_points(grantings, **_kwargs): | ||
logger.info(f"Deletion of evaluation {evaluation} has created {len(grantings)} reward point grantings") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @janno42 evaluation deletion currently just removes the row, it doesn't reload the page, so a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's okay to only put it in the log file. We don't have to show it to managers. |
||
|
||
if not evaluation.can_be_deleted_by_manager: | ||
raise SuspiciousOperation("Deleting evaluation not allowed") | ||
if evaluation.is_single_result: | ||
|
@@ -2296,7 +2307,7 @@ def user_import(request): | |
@manager_required | ||
def user_edit(request, user_id): | ||
# See comment in helper_evaluation_edit | ||
@receiver(RewardPointGranting.granted_by_removal, weak=True) | ||
@receiver(RewardPointGranting.granted_by_participation_removal, weak=True) | ||
def notify_reward_points(grantings, **_kwargs): | ||
assert len(grantings) == 1 | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to prefer this over the following?