Repository: e-valuation/EvaP

Issue:
Find alternative to lastform() method used in many tests.
While trying to implement a feedback form in the base.html file I broke about 1/3 of all tests:

Some 35 test rely on using lastform() to identify the last form in the page. This breaks when any forms are added at the bottom of the page.
If I add a form to the top, about 15 other tests break. They rely on the form to be tested to be at a certain index.

A fix would be to name all forms and call them by their identifiers instead of relying on their location.



Discussion:
I would very much appreciate naming all forms.


Resolving patch:
diff --git a/evap/staff/urls.py b/evap/staff/urls.py
--- a/evap/staff/urls.py
+++ b/evap/staff/urls.py
@@ -15,14 +15,14 @@
     url(r"^semester/(\d+)/edit$", semester_edit, name="semester_edit"),
     url(r"^semester/(\d+)/import$", semester_import, name="semester_import"),
     url(r"^semester/(\d+)/export$", semester_export, name="semester_export"),
-    url(r"^semester/(\d+)/assign$", semester_assign_questionnaires, name="semester_assign_questionnaires"),
+    url(r"^semester/(\d+)/assign$", semester_questionnaire_assign, name="semester_questionnaire_assign"),
     url(r"^semester/(\d+)/todo", semester_todo, name="semester_todo"),
     url(r"^semester/(\d+)/lottery$", semester_lottery, name="semester_lottery"),
     url(r"^semester/(\d+)/course/create$", course_create, name="course_create"),
     url(r"^semester/(\d+)/course/(\d+)/edit$", course_edit, name="course_edit"),
     url(r"^semester/(\d+)/course/(\d+)/email$", course_email, name="course_email"),
     url(r"^semester/(\d+)/course/(\d+)/preview$", course_preview, name="course_preview"),
-    url(r"^semester/(\d+)/course/(\d+)/importparticipants", course_import_participants, name="course_import_participants"),
+    url(r"^semester/(\d+)/course/(\d+)/participant_import", course_participant_import, name="course_participant_import"),
     url(r"^semester/(\d+)/course/(\d+)/comments$", course_comments, name="course_comments"),
     url(r"^semester/(\d+)/course/(\d+)/comment/(\d+)/edit$", course_comment_edit, name="course_comment_edit"),
     url(r"^semester/(\d+)/courseoperation$", semester_course_operation, name="semester_course_operation"),
diff --git a/evap/staff/views.py b/evap/staff/views.py
--- a/evap/staff/views.py
+++ b/evap/staff/views.py
@@ -324,10 +324,10 @@ def semester_import(request, semester_id):
         # parse table
         EnrollmentImporter.process(request, excel_file, semester, vote_start_date, vote_end_date, test_run)
         if test_run:
-            return render(request, "staff_import.html", dict(semester=semester, form=form))
+            return render(request, "staff_semester_import.html", dict(semester=semester, form=form))
         return redirect('staff:semester_view', semester_id)
     else:
-        return render(request, "staff_import.html", dict(semester=semester, form=form))
+        return render(request, "staff_semester_import.html", dict(semester=semester, form=form))
 
 
 @staff_required
@@ -355,7 +355,7 @@ def semester_export(request, semester_id):
 
 
 @staff_required
-def semester_assign_questionnaires(request, semester_id):
+def semester_questionnaire_assign(request, semester_id):
     semester = get_object_or_404(Semester, id=semester_id)
     raise_permission_denied_if_archived(semester)
     courses = semester.course_set.filter(state='new')
@@ -373,7 +373,7 @@ def semester_assign_questionnaires(request, semester_id):
         messages.success(request, _("Successfully assigned questionnaires."))
         return redirect('staff:semester_view', semester_id)
     else:
-        return render(request, "staff_semester_assign_questionnaires.html", dict(semester=semester, form=form))
+        return render(request, "staff_semester_questionnaire_assign_form.html", dict(semester=semester, form=form))
 
 
 @staff_required
@@ -571,7 +571,7 @@ def course_email(request, semester_id, course_id):
 
 
 @staff_required
-def course_import_participants(request, semester_id, course_id):
+def course_participant_import(request, semester_id, course_id):
     course = get_object_or_404(Course, id=course_id)
     semester = get_object_or_404(Semester, id=semester_id)
     raise_permission_denied_if_archived(course)
@@ -593,14 +593,14 @@ def course_import_participants(request, semester_id, course_id):
 
         # Test run, or an error occurred while parsing -> stay and display error.
         if test_run or not imported_users:
-            return render(request, "staff_import_participants.html", dict(course=course, form=form))
+            return render(request, "staff_course_participant_import.html", dict(course=course, form=form))
         else:
             # Add users to course participants. * converts list into parameters.
             course.participants.add(*imported_users)
             messages.success(request, "%d Participants added to course %s" % (len(imported_users), course.name))
             return redirect('staff:semester_view', semester_id)
     else:
-        return render(request, "staff_import_participants.html", dict(course=course, form=form, semester=semester))
+        return render(request, "staff_course_participant_import.html", dict(course=course, form=form, semester=semester))
 
 
 @staff_required


Test patch:
diff --git a/evap/contributor/tests/test_views.py b/evap/contributor/tests/test_views.py
--- a/evap/contributor/tests/test_views.py
+++ b/evap/contributor/tests/test_views.py
@@ -1,7 +1,7 @@
 from webtest.app import AppError
 
 from evap.evaluation.models import Course, Questionnaire
-from evap.evaluation.tests.test_utils import ViewTest, course_with_responsible_and_editor, lastform
+from evap.evaluation.tests.test_utils import ViewTest, course_with_responsible_and_editor
 
 TESTING_COURSE_ID = 2
 
@@ -84,7 +84,7 @@ def test_contributor_course_edit(self):
         course = Course.objects.get(pk=TESTING_COURSE_ID)
 
         page = self.get_assert_200("/contributor/course/{}/edit".format(TESTING_COURSE_ID), user="responsible")
-        form = lastform(page)
+        form = page.forms["course-form"]
         form["vote_start_date"] = "02/1/2098"
         form["vote_end_date"] = "02/1/2099"
 
diff --git a/evap/evaluation/tests/test_coverage.py b/evap/evaluation/tests/test_coverage.py
--- a/evap/evaluation/tests/test_coverage.py
+++ b/evap/evaluation/tests/test_coverage.py
@@ -4,7 +4,7 @@
 
 from evap.evaluation.models import Semester, Questionnaire, UserProfile, Course, \
                             EmailTemplate, Degree, CourseType, Contribution
-from evap.evaluation.tests.test_utils import WebTest, lastform
+from evap.evaluation.tests.test_utils import WebTest
 from evap.staff.forms import ContributionFormSet, ContributionForm
 
 from model_mommy import mommy
@@ -231,7 +231,7 @@ def helper_semester_state_views(self, course_ids, old_state, new_state, operatio
         form['course'] = course_ids
         response = form.submit('operation', value=operation)
 
-        form = lastform(response)
+        form = response.forms["course-operation-form"]
         response = form.submit()
         self.assertIn("Successfully", str(response))
         for course_id in course_ids:
@@ -271,7 +271,7 @@ def test_course_create(self):
         data = dict(name_de="asdf", name_en="asdf", type=1, degrees=["1"],
                     vote_start_date="02/1/2014", vote_end_date="02/1/2099", general_questions=["2"])
         response = self.get_assert_200("/staff/semester/1/course/create", "evap")
-        form = lastform(response)
+        form = response.forms["course-form"]
         form["name_de"] = "lfo9e7bmxp1xi"
         form["name_en"] = "asdf"
         form["type"] = 1
@@ -304,7 +304,7 @@ def test_single_result_create(self):
             Tests the single result creation view with one valid and one invalid input dataset.
         """
         response = self.get_assert_200("/staff/semester/1/singleresult/create", "evap")
-        form = lastform(response)
+        form = response.forms["single-result-form"]
         form["name_de"] = "qwertz"
         form["name_en"] = "qwertz"
         form["type"] = 1
@@ -327,7 +327,7 @@ def test_course_email(self):
             Tests whether the course email view actually sends emails.
         """
         page = self.get_assert_200("/staff/semester/1/course/5/email", user="evap")
-        form = lastform(page)
+        form = page.forms["course-email-form"]
         form.get("recipients", index=0).checked = True # send to all participants
         form["subject"] = "asdf"
         form["body"] = "asdf"
@@ -355,7 +355,7 @@ def test_create_user(self):
             Tests whether the user creation view actually creates a user.
         """
         page = self.get_assert_200("/staff/user/create", "evap")
-        form = lastform(page)
+        form = page.forms["user-form"]
         form["username"] = "mflkd862xmnbo5"
         form["first_name"] = "asd"
         form["last_name"] = "asd"
@@ -370,7 +370,7 @@ def test_emailtemplate(self):
             Tests the emailtemplate view with one valid and one invalid input datasets.
         """
         page = self.get_assert_200("/staff/template/1", "evap")
-        form = lastform(page)
+        form = page.forms["template-form"]
         form["subject"] = "subject: mflkd862xmnbo5"
         form["body"] = "body: mflkd862xmnbo5"
         form.submit()
@@ -389,7 +389,7 @@ def test_student_vote(self):
             the course a second time.
         """
         page = self.get_assert_200("/student/vote/5", user="lazy.student")
-        form = lastform(page)
+        form = page.forms["student-vote-form"]
         form["question_17_2_3"] = "some text"
         form["question_17_2_4"] = 1
         form["question_17_2_5"] = 6
@@ -401,7 +401,7 @@ def test_student_vote(self):
         response = form.submit()
 
         self.assertIn("vote for all rating questions", response)
-        form = lastform(page)
+        form = page.forms["student-vote-form"]
         self.assertEqual(form["question_17_2_3"].value, "some text")
         self.assertEqual(form["question_17_2_4"].value, "1")
         self.assertEqual(form["question_17_2_5"].value, "6")
@@ -420,7 +420,7 @@ def test_course_type_form(self):
             Adds a course type via the staff form and verifies that the type was created in the db.
         """
         page = self.get_assert_200("/staff/course_types/", user="evap")
-        form = lastform(page)
+        form = page.forms["course-type-form"]
         last_form_id = int(form["form-TOTAL_FORMS"].value) - 1
         form["form-" + str(last_form_id) + "-name_de"].value = "Test"
         form["form-" + str(last_form_id) + "-name_en"].value = "Test"
@@ -434,7 +434,7 @@ def test_degree_form(self):
             Adds a degree via the staff form and verifies that the degree was created in the db.
         """
         page = self.get_assert_200("/staff/degrees/", user="evap")
-        form = lastform(page)
+        form = page.forms["degree-form"]
         last_form_id = int(form["form-TOTAL_FORMS"].value) - 1
         form["form-" + str(last_form_id) + "-name_de"].value = "Test"
         form["form-" + str(last_form_id) + "-name_en"].value = "Test"
@@ -454,7 +454,7 @@ def test_course_type_merge(self):
         self.assertGreater(courses_with_other_type.count(), 0)
 
         page = self.get_assert_200("/staff/course_types/" + str(main_type.pk) + "/merge/" + str(other_type.pk), user="evap")
-        form = lastform(page)
+        form = page.forms["course-type-merge-form"]
         response = form.submit()
         self.assertIn("Successfully", str(response))
 
diff --git a/evap/evaluation/tests/test_misc.py b/evap/evaluation/tests/test_misc.py
--- a/evap/evaluation/tests/test_misc.py
+++ b/evap/evaluation/tests/test_misc.py
@@ -9,7 +9,7 @@
 from model_mommy import mommy
 
 from evap.evaluation.models import Semester, UserProfile, CourseType
-from evap.evaluation.tests.test_utils import WebTest, lastform
+from evap.evaluation.tests.test_utils import WebTest
 
 
 @override_settings(INSTITUTION_EMAIL_DOMAINS=["institution.com", "student.institution.com"])
@@ -27,7 +27,7 @@ def test_sample_xls(self):
 
         original_user_count = UserProfile.objects.count()
 
-        form = lastform(page)
+        form = page.forms["semester-import-form"]
         form["vote_start_date"] = "2015-01-01"
         form["vote_end_date"] = "2099-01-01"
         form["excel_file"] = (os.path.join(settings.BASE_DIR, "static", "sample.xls"),)
@@ -40,7 +40,7 @@ def test_sample_user_xls(self):
 
         original_user_count = UserProfile.objects.count()
 
-        form = lastform(page)
+        form = page.forms["user-import-form"]
         form["excel_file"] = (os.path.join(settings.BASE_DIR, "static", "sample_user.xls"),)
         form.submit(name="operation", value="import")
 
diff --git a/evap/evaluation/tests/test_utils.py b/evap/evaluation/tests/test_utils.py
--- a/evap/evaluation/tests/test_utils.py
+++ b/evap/evaluation/tests/test_utils.py
@@ -58,10 +58,6 @@ def test_check_response_code_200(self):
             self.get_assert_200(self.url, user)
 
 
-def lastform(page):
-    return page.forms[max(key for key in page.forms.keys() if isinstance(key, int))]
-
-
 def get_form_data_from_instance(FormClass, instance):
     assert FormClass._meta.model == type(instance)
     form = FormClass(instance=instance)
diff --git a/evap/rewards/tests.py b/evap/rewards/tests.py
--- a/evap/rewards/tests.py
+++ b/evap/rewards/tests.py
@@ -5,7 +5,7 @@
 
 from evap.evaluation.models import Course
 from evap.evaluation.models import UserProfile
-from evap.evaluation.tests.test_utils import WebTest, lastform
+from evap.evaluation.tests.test_utils import WebTest
 from evap.rewards.models import SemesterActivation
 from evap.rewards.models import RewardPointRedemptionEvent
 from evap.rewards.tools import reward_points_of_user
@@ -39,7 +39,7 @@ def test_redeem_reward_points(self):
         self.assertEqual(response.status_code, 200)
 
         user = UserProfile.objects.get(pk=5)
-        form = lastform(response)
+        form = response.forms["reward-redemption-form"]
         form.set("points-1", reward_points_of_user(user))
         response = form.submit()
         self.assertEqual(response.status_code, 200)
@@ -57,7 +57,7 @@ def test_create_redemption_event(self):
         """
         response = self.app.get(reverse("rewards:reward_point_redemption_event_create"), user="evap")
 
-        form = lastform(response)
+        form = response.forms["reward-point-redemption-event-form"]
         form.set('name', 'Test3Event')
         form.set('date', '2014-12-10')
         form.set('redeem_end_date', '2014-11-20')
@@ -72,7 +72,7 @@ def test_edit_redemption_event(self):
         """
         response = self.app.get(reverse("rewards:reward_point_redemption_event_edit", args=[2]), user="evap")
 
-        form = lastform(response)
+        form = response.forms["reward-point-redemption-event-form"]
         name = form.get('name').value
         form.set('name', 'new name')
 
@@ -89,7 +89,7 @@ def test_grant_reward_points(self):
         reward_points_before_end = reward_points_of_user(user)
         response = self.app.get(reverse("student:vote", args=[9]), user="student")
 
-        form = lastform(response)
+        form = response.forms["student-vote-form"]
         for key, value in form.fields.items():
             if key is not None and "question" in key:
                 form.set(key, 6)
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
@@ -8,7 +8,7 @@
 
 from evap.evaluation.models import Semester, Questionnaire, Question, UserProfile, Course, \
                             CourseType, Contribution
-from evap.evaluation.tests.test_utils import WebTest, lastform
+from evap.evaluation.tests.test_utils import WebTest
 
 
 class UsecaseTests(WebTest):
@@ -24,7 +24,7 @@ def test_import(self):
 
         # create a new semester
         page = page.click("[Cc]reate [Nn]ew [Ss]emester")
-        semester_form = lastform(page)
+        semester_form = page.forms["semester-form"]
         semester_form['name_de'] = "Testsemester"
         semester_form['name_en'] = "test semester"
         page = semester_form.submit().follow()
@@ -40,7 +40,7 @@ def test_import(self):
 
         # import excel file
         page = page.click("[Ii]mport")
-        upload_form = lastform(page)
+        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"),)
@@ -78,7 +78,7 @@ def test_create_questionnaire(self):
 
         # create a new questionnaire
         page = page.click("[Cc]reate [Nn]ew [Qq]uestionnaire")
-        questionnaire_form = lastform(page)
+        questionnaire_form = page.forms["questionnaire-form"]
         questionnaire_form['name_de'] = "Test Fragebogen"
         questionnaire_form['name_en'] = "test questionnaire"
         questionnaire_form['public_name_de'] = "Oeffentlicher Test Fragebogen"
@@ -98,7 +98,7 @@ def test_create_empty_questionnaire(self):
 
         # create a new questionnaire
         page = page.click("[Cc]reate [Nn]ew [Qq]uestionnaire")
-        questionnaire_form = lastform(page)
+        questionnaire_form = page.forms["questionnaire-form"]
         questionnaire_form['name_de'] = "Test Fragebogen"
         questionnaire_form['name_en'] = "test questionnaire"
         questionnaire_form['public_name_de'] = "Oeffentlicher Test Fragebogen"
@@ -120,7 +120,7 @@ def test_copy_questionnaire(self):
         # create a new questionnaire
         page = page.click("All questionnaires")
         page = page.click("Copy", index=1)
-        questionnaire_form = lastform(page)
+        questionnaire_form = page.forms["questionnaire-form"]
         questionnaire_form['name_de'] = "Test Fragebogen (kopiert)"
         questionnaire_form['name_en'] = "test questionnaire (copied)"
         questionnaire_form['public_name_de'] = "Oeffentlicher Test Fragebogen (kopiert)"
@@ -145,7 +145,7 @@ def test_assign_questionnaires(self):
         # assign questionnaire to courses
         page = page.click("Semester 1", index=0)
         page = page.click("Assign Questionnaires")
-        assign_form = lastform(page)
+        assign_form = page.forms["questionnaire-assign-form"]
         assign_form['Seminar'] = [questionnaire.pk]
         assign_form['Vorlesung'] = [questionnaire.pk]
         page = assign_form.submit().follow()
@@ -163,7 +163,7 @@ def test_remove_responsibility(self):
         page = page.click(contribution.course.name_en)
 
         # remove responsibility
-        form = lastform(page)
+        form = page.forms["course-form"]
         form['contributions-0-responsibility'] = "CONTRIBUTOR"
         page = form.submit()
 
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
@@ -9,7 +9,7 @@
 
 from evap.evaluation.models import Semester, UserProfile, Course, CourseType, \
                                    TextAnswer, Contribution
-from evap.evaluation.tests.test_utils import FuzzyInt, lastform, WebTest, ViewTest
+from evap.evaluation.tests.test_utils import FuzzyInt, WebTest, ViewTest
 
 
 class TestUserIndexView(ViewTest):
@@ -43,7 +43,7 @@ def setUpTestData(cls):
 
     def test_testrun_deletes_no_users(self):
         page = self.app.get(self.url, user='staff')
-        form = lastform(page)
+        form = page.forms["user-bulk-delete-form"]
 
         form["username_file"] = (self.filename,)
 
@@ -62,7 +62,7 @@ def test_deletes_users(self):
         contribution = mommy.make(Contribution)
         mommy.make(UserProfile, username='contributor', contributions=[contribution])
         page = self.app.get(self.url, user='staff')
-        form = lastform(page)
+        form = page.forms["user-bulk-delete-form"]
 
         form["username_file"] = (self.filename,)
 
@@ -93,7 +93,7 @@ def setUpTestData(cls):
 
     def test_view_downloads_excel_file(self):
         page = self.app.get(self.url, user='staff')
-        form = lastform(page)
+        form = page.forms["semester-export-form"]
 
         # Check one course type.
         form.set('form-0-selected_course_types', 'id_form-0-selected_course_types_0')
@@ -108,7 +108,7 @@ def test_view_downloads_excel_file(self):
 
 @override_settings(INSTITUTION_EMAIL_DOMAINS=["institution.com", "student.institution.com"])
 class TestSemesterCourseImportParticipantsView(ViewTest):
-    url = "/staff/semester/1/course/1/importparticipants"
+    url = "/staff/semester/1/course/1/participant_import"
     test_users = ["staff"]
     filename_valid = os.path.join(settings.BASE_DIR, "staff/fixtures/valid_user_import.xls")
     filename_invalid = os.path.join(settings.BASE_DIR, "staff/fixtures/invalid_user_import.xls")
@@ -124,7 +124,7 @@ def test_import_valid_file(self):
 
         original_participant_count = self.course.participants.count()
 
-        form = lastform(page)
+        form = page.forms["participant-import-form"]
         form["excel_file"] = (self.filename_valid,)
         form.submit(name="operation", value="import")
 
@@ -135,7 +135,7 @@ def test_import_invalid_file(self):
 
         original_user_count = UserProfile.objects.count()
 
-        form = lastform(page)
+        form = page.forms["participant-import-form"]
         form["excel_file"] = (self.filename_invalid,)
 
         reply = form.submit(name="operation", value="import")
@@ -150,7 +150,7 @@ def test_test_run(self):
 
         original_participant_count = self.course.participants.count()
 
-        form = lastform(page)
+        form = page.forms["participant-import-form"]
         form["excel_file"] = (self.filename_valid,)
         form.submit(name="operation", value="test")
 
@@ -159,7 +159,7 @@ def test_test_run(self):
     def test_suspicious_operation(self):
         page = self.app.get(self.url, user='staff')
 
-        form = lastform(page)
+        form = page.forms["participant-import-form"]
         form["excel_file"] = (self.filename_valid,)
 
         # Should throw SuspiciousOperation Exception.