internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: e-valuation/EvaP

Issue:
Make number of enrollments before warning is shown a setting
the importers show a warning `Warning: User {} has {} enrollments, which is a lot.` if a user has more than a certain number of enrollments in the import. this number should be configurable in the settings file.
Make number of enrollments before warning is shown a setting
the importers show a warning `Warning: User {} has {} enrollments, which is a lot.` if a user has more than a certain number of enrollments in the import. this number should be configurable in the settings file.


Discussion:

Resolving patch:
diff --git a/evap/settings.py b/evap/settings.py
--- a/evap/settings.py
+++ b/evap/settings.py
@@ -61,6 +61,9 @@
 IMPORTER_GRADED_YES = "yes"
 IMPORTER_GRADED_NO = "no"
 
+# the importer will warn if any participant has more enrollments than this number
+IMPORTER_MAX_ENROLLMENTS = 7
+
 # the default descriptions for grade documents
 DEFAULT_FINAL_GRADES_DESCRIPTION_EN = "Final grades"
 DEFAULT_MIDTERM_GRADES_DESCRIPTION_EN = "Midterm grades"
diff --git a/evap/staff/importers.py b/evap/staff/importers.py
--- a/evap/staff/importers.py
+++ b/evap/staff/importers.py
@@ -212,7 +212,6 @@ def __init__(self, request):
         # this is a dictionary to not let this become O(n^2)
         self.courses = {}
         self.enrollments = []
-        self.max_enrollments = 6
 
     def read_one_enrollment(self, data):
         student_data = UserData(username=data[3], first_name=data[2], last_name=data[1], email=data[4], title='', is_responsible=False)
@@ -269,7 +268,7 @@ def check_enrollment_data_sanity(self):
         for enrollment in self.enrollments:
             enrollments_per_user[enrollment[1].username].append(enrollment)
         for username, enrollments in enrollments_per_user.items():
-            if len(enrollments) > self.max_enrollments:
+            if len(enrollments) > settings.IMPORTER_MAX_ENROLLMENTS:
                 self.warnings.append(_("Warning: User {} has {} enrollments, which is a lot.").format(username, len(enrollments)))
 
     def write_enrollments_to_db(self, semester, vote_start_date, vote_end_date):


Test patch:
diff --git a/evap/staff/fixtures/test_enrolment_data.xls b/evap/staff/fixtures/test_enrollment_data.xls
similarity index 100%
rename from evap/staff/fixtures/test_enrolment_data.xls
rename to evap/staff/fixtures/test_enrollment_data.xls
diff --git a/evap/staff/tests/test_usecases.py b/evap/staff/tests/test_usecases.py
--- a/evap/staff/tests/test_usecases.py
+++ b/evap/staff/tests/test_usecases.py
@@ -43,7 +43,7 @@ def test_import(self):
         upload_form = page.forms["semester-import-form"]
         upload_form['vote_start_date'] = "02/29/2000"
         upload_form['vote_end_date'] = "02/29/2012"
-        upload_form['excel_file'] = (os.path.join(settings.BASE_DIR, "staff/fixtures/test_enrolment_data.xls"),)
+        upload_form['excel_file'] = (os.path.join(settings.BASE_DIR, "staff/fixtures/test_enrollment_data.xls"),)
         upload_form.submit(name="operation", value="import").follow()
 
         self.assertEqual(UserProfile.objects.count(), original_user_count + 23)
Repository: e-valuation/EvaP

Issue:
Show message on form errors
When a submitted form contains errors an error message should be shown on top of the page in addition to the form's error messages.
This is only needed for course edit pages.


Discussion:

Resolving patch:
diff --git a/evap/contributor/views.py b/evap/contributor/views.py
--- a/evap/contributor/views.py
+++ b/evap/contributor/views.py
@@ -102,6 +102,7 @@ def course_edit(request, course_id):
 
         return redirect('contributor:index')
     else:
+        messages.error(request, _("The form was not saved. Please resolve the errors shown below."))
         sort_formset(request, formset)
         template_data = dict(form=course_form, formset=formset, course=course, editable=True, responsible=course.responsible_contributor.username)
         return render(request, "contributor_course_form.html", template_data)
diff --git a/evap/staff/views.py b/evap/staff/views.py
--- a/evap/staff/views.py
+++ b/evap/staff/views.py
@@ -569,6 +569,7 @@ def helper_course_edit(request, semester, course):
 
         return custom_redirect('staff:semester_view', semester.id)
     else:
+        messages.error(request, _("The form was not saved. Please resolve the errors shown below."))
         sort_formset(request, formset)
         template_data = dict(course=course, semester=semester, form=form, formset=formset, staff=True, state=course.state, editable=editable)
         return render(request, "staff_course_form.html", template_data)


Test patch:
Repository: e-valuation/EvaP

Issue:
Whenever editing a course, it says "The form was not saved"
The title says it all: Whenever editing a course, it gives the error message "The form was not saved"

Likely introduced in https://github.com/fsr-itse/EvaP/commit/5f78c332eb59df40f8c61d3045926f4431f43dc9


Discussion:
a check `if form.errors` should be added

Resolving patch:
diff --git a/evap/contributor/views.py b/evap/contributor/views.py
--- a/evap/contributor/views.py
+++ b/evap/contributor/views.py
@@ -102,7 +102,8 @@ def course_edit(request, course_id):
 
         return redirect('contributor:index')
     else:
-        messages.error(request, _("The form was not saved. Please resolve the errors shown below."))
+        if course_form.errors or formset.errors:
+            messages.error(request, _("The form was not saved. Please resolve the errors shown below."))
         sort_formset(request, formset)
         template_data = dict(form=course_form, formset=formset, course=course, editable=True, responsible=course.responsible_contributor.username)
         return render(request, "contributor_course_form.html", template_data)
diff --git a/evap/staff/views.py b/evap/staff/views.py
--- a/evap/staff/views.py
+++ b/evap/staff/views.py
@@ -569,7 +569,8 @@ def helper_course_edit(request, semester, course):
 
         return custom_redirect('staff:semester_view', semester.id)
     else:
-        messages.error(request, _("The form was not saved. Please resolve the errors shown below."))
+        if form.errors or formset.errors:
+            messages.error(request, _("The form was not saved. Please resolve the errors shown below."))
         sort_formset(request, formset)
         template_data = dict(course=course, semester=semester, form=form, formset=formset, staff=True, state=course.state, editable=editable)
         return render(request, "staff_course_form.html", template_data)


Test patch:
Repository: e-valuation/EvaP

Issue:
Allow editing questionnaires that are only used in new courses
Editing questionnaires should be enabled for questionnaires that are in use if all courses which are using the questionnaire are in the state `new`.


Discussion:

Resolving patch:
diff --git a/evap/evaluation/models.py b/evap/evaluation/models.py
--- a/evap/evaluation/models.py
+++ b/evap/evaluation/models.py
@@ -126,11 +126,11 @@ def __gt__(self, other):
 
     @property
     def can_staff_edit(self):
-        return not self.contributions.exists()
+        return not self.contributions.exclude(course__state='new').exists()
 
     @property
     def can_staff_delete(self):
-        return self.can_staff_edit
+        return not self.contributions.exists()
 
     @property
     def text_questions(self):


Test patch:
Repository: e-valuation/EvaP

Issue:
Warning if email address is missing when sending emails for course
When sending emails for a course (e.g. when publishing it), a warning must be shown if the email address is missing for a user to whom an email should be sent.
This can be a Django warning in the UI when sending the emails was triggered by a staff user, but must be handled differently when the situation occurrs when a cronjob triggers it. Maybe a message should be send to the admins.



Discussion:
all mail is sent through EmailTemplate.__send_to_user(). So, the plan would be to find out whether a user is logged in or a cronjob triggered the email sending, and then, in that method, either send an email to the admins or display a django message for each missing email address. the existing handling in staff.views.course_email can probably be removed then.


Resolving patch:
diff --git a/evap/evaluation/models.py b/evap/evaluation/models.py
--- a/evap/evaluation/models.py
+++ b/evap/evaluation/models.py
@@ -1111,9 +1111,14 @@ def send_to_users_in_courses(cls, template, courses, recipient_groups, use_cc, r
     def send_to_user(cls, user, template, subject_params, body_params, use_cc, request=None):
         if not user.email:
             warning_message = "{} has no email address defined. Could not send email.".format(user.username)
-            logger.warning(warning_message)
+            # If this method is triggered by a cronjob changing course states, the request is None.
+            # In this case warnings should be sent to the admins via email (configured in the settings for logger.error).
+            # If a request exists, the page is displayed in the browser and the message can be shown on the page (messages.warning).
             if request is not None:
+                logger.warning(warning_message)
                 messages.warning(request, _(warning_message))
+            else:
+                logger.error(warning_message)
             return
 
         if use_cc:


Test patch:
Repository: e-valuation/EvaP

Issue:
Staff users should see the public view of results by default
Staff users should by default see the public view of results pages. It shows less data (primarily no comments), which is good, and random people looking over one's shoulder won't ask "omg you see more stuff here why is that"


Discussion:

Resolving patch:
diff --git a/evap/results/views.py b/evap/results/views.py
--- a/evap/results/views.py
+++ b/evap/results/views.py
@@ -58,7 +58,10 @@ def course_detail(request, semester_id, course_id):
 
     sections = calculate_results(course)
 
-    public_view = request.GET.get('public_view') == 'true'  # if parameter is not given, show own view.
+    if request.user.is_staff or request.user.is_reviewer:
+        public_view = request.GET.get('public_view') != 'false'  # if parameter is not given, show public view.
+    else:
+        public_view = request.GET.get('public_view') == 'true'  # if parameter is not given, show own view.
 
     represented_users = list(request.user.represented_users.all())
     represented_users.append(request.user)


Test patch:
diff --git a/evap/results/tests/test_views.py b/evap/results/tests/test_views.py
--- a/evap/results/tests/test_views.py
+++ b/evap/results/tests/test_views.py
@@ -57,6 +57,16 @@ def test_single_result_course(self):
         user = 'evap'
         self.get_assert_200(url, user)
 
+    def test_default_view_is_public(self):
+        url = '/results/semester/%s' % (self.semester.id)
+        page_without_get_parameter = self.app.get(url, user='evap')
+        url = '/results/semester/%s?public_view=true' % (self.semester.id)
+        page_with_get_parameter = self.app.get(url, user='evap')
+        url = '/results/semester/%s?public_view=asdf' % (self.semester.id)
+        page_with_random_get_parameter = self.app.get(url, user='evap')
+        self.assertEqual(page_without_get_parameter.body, page_with_get_parameter.body)
+        self.assertEqual(page_without_get_parameter.body, page_with_random_get_parameter.body)
+
     def test_wrong_state(self):
         course = mommy.make(Course, state='reviewed', semester=self.semester)
         url = '/results/semester/%s/course/%s' % (self.semester.id, course.id)
Repository: e-valuation/EvaP

Issue:
Clean up semester statistics
The semester statistics on top of the staff semester view should not include courses which are in state `new`. Some courses are created but are never evaluated and do currently influence the participation percentage although they shouldn't.


Discussion:

Resolving patch:
diff --git a/evap/staff/views.py b/evap/staff/views.py
--- a/evap/staff/views.py
+++ b/evap/staff/views.py
@@ -119,9 +119,10 @@ def __init__(self):
                 stats.num_comments_reviewed += course.num_reviewed_textanswers
             if course.state in ['evaluated', 'reviewed', 'published']:
                 stats.num_courses_evaluated += 1
-            stats.num_courses += 1
-            stats.first_start = min(stats.first_start, course.vote_start_datetime)
-            stats.last_end = max(stats.last_end, course.vote_end_date)
+            if course.state != 'new':
+                stats.num_courses += 1
+                stats.first_start = min(stats.first_start, course.vote_start_datetime)
+                stats.last_end = max(stats.last_end, course.vote_end_date)
     degree_stats = OrderedDict(sorted(degree_stats.items(), key=lambda x: x[0].order))
     degree_stats['total'] = total_stats
 


Test patch:
Repository: e-valuation/EvaP

Issue:
List of unpublished courses
Staff users should get a new view with an overview of all graded courses in a semester where the evaluation period ended and no grade information was uploaded yet. The list should be grouped by responsible contributor and should include the course types. This allows them to remind about missing grade uploads.
The resulting text should be optimized for copy paste.

Example:

---
**Prof. Dr. Always Late**
- Random stuff (Bachelor's Project)

**Prof. Dr. Responsible**
- Interesting course (Seminar)
- Something strange (Lecture)


Discussion:
Button "Grade Publish Reminder" on staff overview page


Resolving patch:
diff --git a/evap/staff/urls.py b/evap/staff/urls.py
--- a/evap/staff/urls.py
+++ b/evap/staff/urls.py
@@ -19,6 +19,7 @@
     path("semester/<int:semester_id>/participation_export", views.semester_participation_export, name="semester_participation_export"),
     path("semester/<int:semester_id>/assign", views.semester_questionnaire_assign, name="semester_questionnaire_assign"),
     path("semester/<int:semester_id>/todo", views.semester_todo, name="semester_todo"),
+    path("semester/<int:semester_id>/grade_reminder", views.semester_grade_reminder, name="semester_grade_reminder"),
     path("semester/<int:semester_id>/course/create", views.course_create, name="course_create"),
     path("semester/<int:semester_id>/course/<int:course_id>/edit", views.course_edit, name="course_edit"),
     path("semester/<int:semester_id>/course/<int:course_id>/email", views.course_email, name="course_email"),
diff --git a/evap/staff/views.py b/evap/staff/views.py
--- a/evap/staff/views.py
+++ b/evap/staff/views.py
@@ -489,6 +489,23 @@ def semester_todo(request, semester_id):
     template_data = dict(semester=semester, responsible_list=responsible_list)
     return render(request, "staff_semester_todo.html", template_data)
 
+@staff_required
+def semester_grade_reminder(request, semester_id):
+    semester = get_object_or_404(Semester, id=semester_id)
+
+    courses = semester.course_set.filter(state__in=['evaluated', 'reviewed', 'published'], is_graded=True, gets_no_grade_documents=False).all()
+    courses = [course for course in courses if not course.final_grade_documents.exists()]
+
+    responsibles = (contributor for course in courses for contributor in course.responsible_contributors)
+    responsibles = list(set(responsibles))
+    responsibles.sort(key=lambda responsible: (responsible.last_name, responsible.first_name))
+
+    responsible_list = [(responsible, [course for course in courses if responsible in course.responsible_contributors]) 
+                        for responsible in responsibles]
+
+    template_data = dict(semester=semester, responsible_list=responsible_list)
+    return render(request, "staff_semester_grade_reminder.html", template_data)
+
 
 @staff_required
 def send_reminder(request, semester_id, responsible_id):


Test patch:
Repository: e-valuation/EvaP

Issue:
Test failure depending on time of day
We currently have 4 tests that fail if your system time is between 00:00 and 00:00 + EVALUATION_END_OFFSET_HOURS:

- evap.evaluation.tests.test_models.TestCourses:test_evaluation_ended
- evap.evaluation.tests.test_models.TestCourses:test_in_evaluation_to_evalued
- evap.evaluation.tests.test_models.TestCourses:test_in_evaluation_to_published
- evap.evaluation.tests.test_models.TestCourses:test_in_evaluation_to_reviewed


Discussion:
I'll work on this now. 

Resolving patch:
diff --git a/evap/evaluation/models.py b/evap/evaluation/models.py
--- a/evap/evaluation/models.py
+++ b/evap/evaluation/models.py
@@ -21,7 +21,6 @@
 # see evaluation.meta for the use of Translate in this file
 from evap.evaluation.meta import LocalizeModelBase, Translate
 from evap.evaluation.tools import date_to_datetime, get_due_courses_for_user
-from evap.settings import EVALUATION_END_OFFSET_HOURS, EVALUATION_END_WARNING_PERIOD
 
 logger = logging.getLogger(__name__)
 
@@ -272,7 +271,7 @@ def is_fully_reviewed(self):
     @property
     def vote_end_datetime(self):
         # The evaluation ends at EVALUATION_END_OFFSET_HOURS:00 of the day AFTER self.vote_end_date.
-        return date_to_datetime(self.vote_end_date) + timedelta(hours=24 + EVALUATION_END_OFFSET_HOURS)
+        return date_to_datetime(self.vote_end_date) + timedelta(hours=24 + settings.EVALUATION_END_OFFSET_HOURS)
 
     @property
     def is_in_evaluation_period(self):
@@ -436,7 +435,7 @@ def time_left_for_evaluation(self):
         return self.vote_end_datetime - datetime.now()
 
     def evaluation_ends_soon(self):
-        return self.time_left_for_evaluation.total_seconds() < EVALUATION_END_WARNING_PERIOD * 3600
+        return self.time_left_for_evaluation.total_seconds() < settings.EVALUATION_END_WARNING_PERIOD * 3600
 
     @property
     def days_until_evaluation(self):


Test patch:
diff --git a/evap/evaluation/tests/test_models.py b/evap/evaluation/tests/test_models.py
--- a/evap/evaluation/tests/test_models.py
+++ b/evap/evaluation/tests/test_models.py
@@ -11,7 +11,6 @@
 from evap.evaluation.models import (Contribution, Course, CourseType, EmailTemplate, NotArchiveable, Questionnaire,
                                     RatingAnswerCounter, Semester, UserProfile)
 from evap.results.tools import calculate_average_grades_and_deviation
-from evap.settings import EVALUATION_END_OFFSET_HOURS, EVALUATION_END_WARNING_PERIOD
 
 
 @override_settings(EVALUATION_END_OFFSET_HOURS=0)
Repository: e-valuation/EvaP

Issue:
Line above contribution questionnaires list
3ece634e8fb13450f4a18c0479f241d516acc5ec added a division line between top and bottom course questionnaires. this lead to a wrongly placed line in the contribution form questionnaires list, which should be removed:

![line](https://user-images.githubusercontent.com/1781719/40080067-2823b104-588a-11e8-9e8d-bffd5834a846.PNG)



Discussion:

Resolving patch:
diff --git a/evap/evaluation/models.py b/evap/evaluation/models.py
--- a/evap/evaluation/models.py
+++ b/evap/evaluation/models.py
@@ -470,7 +470,10 @@ def evaluation_ends_soon(self):
 
     @property
     def days_until_evaluation(self):
-        return (self.vote_start_datetime.date() - date.today()).days
+        days_left = (self.vote_start_datetime.date() - date.today()).days
+        if self.vote_start_datetime < datetime.now():
+            days_left -= 1
+        return days_left
 
     def is_user_editor_or_delegate(self, user):
         if self.contributions.filter(can_edit=True, contributor=user).exists():
diff --git a/evap/staff/views.py b/evap/staff/views.py
--- a/evap/staff/views.py
+++ b/evap/staff/views.py
@@ -148,7 +148,10 @@ def semester_course_operation(request, semester_id):
         raise SuspiciousOperation("Unknown target state: " + target_state)
 
     course_ids = (request.GET if request.method == 'GET' else request.POST).getlist('course')
-    courses = Course.objects.filter(id__in=course_ids)
+    courses = Course.objects.filter(id__in=course_ids).annotate(
+        midterm_grade_documents_count=Count("grade_documents", filter=Q(grade_documents__type=GradeDocument.MIDTERM_GRADES), distinct=True),
+        final_grade_documents_count=Count("grade_documents", filter=Q(grade_documents__type=GradeDocument.FINAL_GRADES), distinct=True)
+    )
 
     if request.method == 'POST':
         template = None


Test patch:
Repository: e-valuation/EvaP

Issue:
Contact modal broken in Firefox
The contact modal does not work in Firefox, because `event` is undefined. Chrome provides this in global scope, that's why it's working there (see https://stackoverflow.com/questions/18274383/ajax-post-working-in-chrome-but-not-in-firefox).


Discussion:

Resolving patch:
diff --git a/evap/evaluation/views.py b/evap/evaluation/views.py
--- a/evap/evaluation/views.py
+++ b/evap/evaluation/views.py
@@ -4,7 +4,7 @@
 from django.contrib import messages, auth
 from django.contrib.auth.decorators import login_required
 from django.core.mail import EmailMessage
-from django.http import HttpResponse
+from django.http import HttpResponse, HttpResponseBadRequest
 from django.shortcuts import redirect, render
 from django.utils.translation import ugettext as _
 from django.views.decorators.http import require_POST
@@ -103,11 +103,12 @@ def contact(request):
         try:
             mail.send()
             logger.info('Sent contact email: \n{}\n'.format(mail.message()))
+            return HttpResponse()
         except Exception:
             logger.exception('An exception occurred when sending the following contact email:\n{}\n'.format(mail.message()))
             raise
 
-    return HttpResponse()
+    return HttpResponseBadRequest()
 
 
 @require_POST


Test patch:
Repository: e-valuation/EvaP

Issue:
SingleResults don't have general contributions
95f66a720 removed generals contributions for single results. Now e.g. `/staff/semester/21` can't be rendered anymore, because `get_courses_with_prefetched_data` in https://github.com/fsr-itse/EvaP/blob/master/evap/staff/views.py#L75 wants to access the general contribution.
That shouldn't be done for single results and we need tests to make sure that this and other pages with course information don't throw errors.


Discussion:

Resolving patch:
diff --git a/evap/staff/views.py b/evap/staff/views.py
--- a/evap/staff/views.py
+++ b/evap/staff/views.py
@@ -72,7 +72,8 @@ def get_courses_with_prefetched_data(semester):
     voter_counts = semester.courses.annotate(num_voters=Count("voters")).values_list("num_voters", flat=True)
 
     for course, participant_count, voter_count in zip(courses, participant_counts, voter_counts):
-        course.general_contribution = course.general_contribution[0]
+        if not course.is_single_result:
+            course.general_contribution = course.general_contribution[0]
         course.responsible_contributors = [contribution.contributor for contribution in course.responsible_contributions]
         if course._participant_count is None:
             course.num_participants = participant_count


Test patch:
diff --git a/evap/staff/tests/test_views.py b/evap/staff/tests/test_views.py
--- a/evap/staff/tests/test_views.py
+++ b/evap/staff/tests/test_views.py
@@ -7,6 +7,7 @@
 from django.core import mail
 from django.urls import reverse
 from django.test import override_settings
+from django.test.testcases import TestCase
 from model_mommy import mommy
 import xlrd
 
@@ -16,6 +17,7 @@
 from evap.evaluation.tests.tools import FuzzyInt, WebTest, ViewTest
 from evap.rewards.models import SemesterActivation, RewardPointGranting
 from evap.staff.tools import generate_import_filename
+from evap.staff.views import get_courses_with_prefetched_data
 
 
 def helper_delete_all_import_files(user_id):
@@ -376,6 +378,12 @@ def test_access_to_semester_with_archived_results(self):
         self.app.get('/staff/semester/2', user='manager', status=200)
 
 
+class TestGetCoursesWithPrefetchedData(TestCase):
+    def test_get_courses_with_prefetched_data(self):
+        course = mommy.make(Course, is_single_result=True)
+        get_courses_with_prefetched_data(course.semester)
+
+
 class TestSemesterCreateView(ViewTest):
     url = '/staff/semester/create'
     test_users = ['manager']