Skip to content

Commit 131eef2

Browse files
committed
keep memory of deleted evaluations and don't import them again
1 parent f63c3dd commit 131eef2

File tree

10 files changed

+179
-4
lines changed

10 files changed

+179
-4
lines changed

evap/cms/json_importer.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from pydantic import TypeAdapter
1717
from typing_extensions import TypedDict
1818

19+
from evap.cms.models import IgnoredEvaluation
1920
from evap.evaluation.models import (
2021
Contribution,
2122
Course,
@@ -426,7 +427,11 @@ def _import_course_from_unused_exam(self, data: ImportEvent) -> Course | None:
426427
# pylint: disable=too-many-locals
427428
def _import_evaluation( # noqa: PLR0912, PLR0915
428429
self, course: Course, data: ImportEvent, earliest_exam_date: date | None = None
429-
) -> Evaluation:
430+
) -> Evaluation | None:
431+
# Don't import ignored evaluations again
432+
if IgnoredEvaluation.objects.filter(cms_id=data["gguid"]).exists():
433+
return None
434+
430435
try:
431436
evaluation = Evaluation.objects.get(course=course, cms_id=data["gguid"])
432437
except Evaluation.DoesNotExist:
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Generated by Django 5.2 on 2026-01-12 20:23
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
initial = True
9+
10+
dependencies = [
11+
("evaluation", "0160_evaluation_staff_notes"),
12+
]
13+
14+
operations = [
15+
migrations.CreateModel(
16+
name="IgnoredEvaluation",
17+
fields=[
18+
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
19+
(
20+
"cms_id",
21+
models.CharField(max_length=255, unique=True, verbose_name="campus management system id"),
22+
),
23+
("name_de", models.CharField(blank=True, max_length=1024, verbose_name="name (german)")),
24+
("name_en", models.CharField(blank=True, max_length=1024, verbose_name="name (english)")),
25+
("notes", models.TextField(blank=True, verbose_name="notes")),
26+
(
27+
"course",
28+
models.ForeignKey(
29+
on_delete=django.db.models.deletion.CASCADE,
30+
related_name="ignored_evaluations",
31+
to="evaluation.course",
32+
verbose_name="course",
33+
),
34+
),
35+
],
36+
options={
37+
"verbose_name": "ignored evaluation",
38+
"verbose_name_plural": "ignored evaluations",
39+
},
40+
),
41+
]

evap/cms/models.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from django.db import models
2+
from django.utils.translation import gettext_lazy as _
3+
4+
from evap.evaluation.models import Course
5+
from evap.evaluation.tools import translate
6+
7+
8+
class IgnoredEvaluation(models.Model):
9+
"""Model for an evaluation that was deleted and should not be imported again from the CMS"""
10+
11+
# unique reference for import from campus management system
12+
cms_id = models.CharField(verbose_name=_("campus management system id"), unique=True, max_length=255)
13+
14+
name_de = models.CharField(max_length=1024, verbose_name=_("name (german)"), blank=True)
15+
name_en = models.CharField(max_length=1024, verbose_name=_("name (english)"), blank=True)
16+
name = translate(en="name_en", de="name_de")
17+
18+
course = models.ForeignKey(Course, models.CASCADE, verbose_name=_("course"), related_name="ignored_evaluations")
19+
20+
notes = models.TextField(verbose_name=_("notes"), blank=True)
21+
22+
class Meta:
23+
verbose_name = _("ignored evaluation")
24+
verbose_name_plural = _("ignored evaluations")
25+
26+
def __str__(self):
27+
if self.name:
28+
return f"{self.course.name}{self.name}"
29+
return self.course.name

evap/cms/urls.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from django.urls import path
2+
3+
from evap.cms import views
4+
5+
app_name = "cms"
6+
7+
urlpatterns = [
8+
path("ignored_evaluation/delete", views.ignored_evaluation_delete, name="ignored_evaluation_delete"),
9+
]

evap/cms/views.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from django.views.decorators.http import require_POST
2+
3+
from evap.cms.models import IgnoredEvaluation
4+
from evap.evaluation.auth import manager_required
5+
from evap.evaluation.tools import (
6+
HttpResponseNoContent,
7+
get_object_from_dict_pk_entry_or_logged_40x,
8+
)
9+
10+
11+
@require_POST
12+
@manager_required
13+
def ignored_evaluation_delete(request):
14+
ignored_evaluation = get_object_from_dict_pk_entry_or_logged_40x(
15+
IgnoredEvaluation, request.POST, "ignored_evaluation_id"
16+
)
17+
ignored_evaluation.delete()
18+
return HttpResponseNoContent()

evap/staff/templates/staff_course_form.html

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{% extends 'staff_course_base.html' %}
22

3+
{% load static %}
4+
35
{% block content %}
46
{{ block.super }}
57
<h3>
@@ -47,8 +49,59 @@ <h5 class="card-title">{% translate 'Evaluations' %}</h5>
4749
</div>
4850
</div>
4951
{% endif %}
52+
</form>
5053

51-
{% include 'log/logentries.html' with logged_object=course %}
54+
{% if course.ignored_evaluations.count > 0 %}
55+
<form id="ignored-evaluation-deletion-form" custom-success method="POST" action="{% url 'cms:ignored_evaluation_delete' %}">
56+
{% csrf_token %}
57+
</form>
5258

53-
</form>
59+
<script type="module">
60+
import { fadeOutThenRemove } from "{% static 'js/utils.js' %}";
61+
document.getElementById("ignored-evaluation-deletion-form").addEventListener("submit-success", event => {
62+
fadeOutThenRemove(document.getElementById(`ignored-evaluation-row-${event.detail.body.get("ignored_evaluation_id")}`))
63+
});
64+
</script>
65+
<div class="card mb-3">
66+
<div class="card-body">
67+
<h5 class="card-title">{% translate 'Ignored evaluations' %}</h5>
68+
<table class="table table-vertically-aligned">
69+
<thead>
70+
<tr>
71+
<th class="width-percent-35">{% translate 'Name' %}</th>
72+
<th class="width-percent-35">{% translate 'Notes' %}</th>
73+
<th class="width-percent-25">{% translate 'CMS ID' %}</th>
74+
<th class="width-percent-5"></th>
75+
</tr>
76+
</thead>
77+
<tbody>
78+
{% for ignored_evaluation in course.ignored_evaluations.all %}
79+
<tr id="ignored-evaluation-row-{{ ignored_evaluation.id }}">
80+
<td>{{ ignored_evaluation }}</td>
81+
<td>{{ ignored_evaluation.notes|linebreaks }}</td>
82+
<td>{{ ignored_evaluation.cms_id }}</td>
83+
<td class="text-end">
84+
<confirmation-modal type="submit" form="ignored-evaluation-deletion-form" name="ignored_evaluation_id" value="{{ ignored_evaluation.id }}" confirm-button-class="btn-danger">
85+
<span slot="title">{% translate 'Delete ignored evaluation' %}</span>
86+
<span slot="action-text">{% translate 'Delete ignored evaluation' %}</span>
87+
<span slot="question">
88+
{% blocktranslate trimmed %}
89+
Do you really want to delete the ignored evaluation <strong>{{ ignored_evaluation }}</strong>? It will then be imported again on the next CMS import.
90+
{% endblocktranslate %}
91+
</span>
92+
93+
<button slot="show-button" type="button" class="btn btn-sm btn-danger" data-bs-toggle="tooltip" data-bs-placement="top" title="{% translate 'Delete' %}">
94+
<span class="fas fa-fw fa-trash" aria-hidden="true"></span>
95+
</button>
96+
</confirmation-modal>
97+
</td>
98+
</tr>
99+
{% endfor %}
100+
</tbody>
101+
</table>
102+
</div>
103+
</div>
104+
{% endif %}
105+
106+
{% include 'log/logentries.html' with logged_object=course %}
54107
{% endblock %}

evap/staff/templates/staff_semester_view.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,14 +506,16 @@ <h3 class="m-0 me-1">{{ semester.name }}</h3>
506506
<colgroup>
507507
<col />
508508
<col style="width: 14em" />
509-
<col style="width: 12em" />
509+
<col style="width: 10em" />
510+
<col style="width: 10em" />
510511
<col style="width: 9em" />
511512
</colgroup>
512513
<thead>
513514
<tr>
514515
<th class="col-order" data-col="name">{% translate 'Course' %}</th>
515516
<th class="col-order" data-col="responsible">{% translate 'Responsible' %}</th>
516517
<th class="col-order" data-col="evaluation-count">{% translate '#Evaluations' %}</th>
518+
<th class="col-order" data-col="ignored-evaluation-count">{% translate '#Ignored evaluations' %}</th>
517519
<th data-not-searchable></th>
518520
</tr>
519521
</thead>
@@ -541,6 +543,9 @@ <h3 class="m-0 me-1">{{ semester.name }}</h3>
541543
{{ course.evaluations.count }}
542544
{% endif %}
543545
</td>
546+
<td data-col="ignored-evaluation-count" data-order="{{ course.ignored_evaluations.count }}">
547+
{{ course.ignored_evaluations.count }}
548+
</td>
544549
<td class="icon-buttons">
545550
{% if request.user.is_manager %}
546551
<a class="btn btn-sm btn-dark" data-bs-toggle="tooltip"

evap/staff/views.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from django.views.decorators.http import require_POST
4040
from django.views.generic import CreateView, FormView, UpdateView
4141

42+
from evap.cms.models import IgnoredEvaluation
4243
from evap.contributor.views import export_contributor_results
4344
from evap.evaluation.auth import manager_required, reviewer_required, staff_permission_required
4445
from evap.evaluation.models import (
@@ -1400,6 +1401,15 @@ def notify_reward_points(grantings, **_kwargs):
14001401
)
14011402

14021403
with temporary_receiver(RewardPointGranting.granted_by_evaluation_deletion, notify_reward_points):
1404+
if evaluation.cms_id:
1405+
# remember deleted evaluation to prevent the importer from creating it again
1406+
IgnoredEvaluation.objects.create(
1407+
cms_id=evaluation.cms_id,
1408+
name_de=evaluation.name_de,
1409+
name_en=evaluation.name_en,
1410+
course=evaluation.course,
1411+
notes=evaluation.staff_notes,
1412+
)
14031413
evaluation.delete()
14041414
update_template_cache_of_published_evaluations_in_course(evaluation.course)
14051415

evap/static/scss/components/_tables.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,7 @@ tr.evaluation-row:not(.heading-row) {
127127
.table-headerless tr:last-child td {
128128
border-bottom-width: 0;
129129
}
130+
131+
td p {
132+
margin-bottom: 0;
133+
}

evap/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
path("contributor/", include('evap.contributor.urls')),
1414
path("rewards/", include('evap.rewards.urls')),
1515
path("grades/", include('evap.grades.urls')),
16+
path("cms/", include('evap.cms.urls')),
1617

1718
path("logout", django.contrib.auth.views.LogoutView.as_view(next_page="/"), name="django-auth-logout"),
1819
path("oidc/", include('mozilla_django_oidc.urls')),

0 commit comments

Comments
 (0)