Repository: ocadotechnology/codeforlife-portal

Issue:
Dashboard page for non-admin teachers
The redesigned dashboard page only caters to admin teachers. It needs to be updated so it can be properly viewed by non-admin teachers.


Discussion:

Resolving patch:
diff --git a/portal/urls.py b/portal/urls.py
--- a/portal/urls.py
+++ b/portal/urls.py
@@ -69,7 +69,7 @@
 from portal.views.email_new import verify_email as verify_email_new, change_email
 from portal.views.home_new import login_view, logout_view as logout_view_new, register_view, contact as contact_new
 from portal.views.organisation_new import organisation_fuzzy_lookup as organisation_fuzzy_lookup_new, \
-    organisation_manage as organisation_manage_new
+    organisation_manage as organisation_manage_new, organisation_leave as organisation_leave_new
 from portal.views.teacher.teach_new import teacher_classes as teacher_classes_new, teacher_class as teacher_class_new, \
     teacher_view_class, teacher_edit_class as teacher_edit_class_new, teacher_move_class as teacher_move_class_new, \
     teacher_edit_student as teacher_edit_student_new, teacher_student_reset as teacher_student_reset_new, \
@@ -245,6 +245,7 @@
     url(r'^redesign/teach/dashboard/disable_2FA/(?P<pk>[0-9]+)/$', teacher_disable_2FA_new, name='teacher_disable_2FA_new'),
     url(r'^redesign/teach/dashboard/allow_join/(?P<pk>[0-9]+)/$', organisation_allow_join_new, name='organisation_allow_join_new'),
     url(r'^redesign/teach/dashboard/deny_join/(?P<pk>[0-9]+)/$', organisation_deny_join_new, name='organisation_deny_join_new'),
+    url(r'^redesign/teach/dashboard/school/leave/$', organisation_leave_new, name='organisation_leave_new'),
     url(r'^redesign/teach/class/(?P<access_code>[A-Z0-9]+)/$', teacher_view_class, name='view_class'),
     url(r'^redesign/teach/class/delete/(?P<access_code>[A-Z0-9]+)/$', teacher_delete_class_new, name='teacher_delete_class_new'),
     url(r'^redesign/teach/class/(?P<access_code>[A-Z0-9]+)/students/delete/$', teacher_delete_students_new, name='teacher_delete_students_new'),
diff --git a/portal/views/organisation_new.py b/portal/views/organisation_new.py
--- a/portal/views/organisation_new.py
+++ b/portal/views/organisation_new.py
@@ -211,3 +211,44 @@ def process_revoke_request(request, teacher):
 @user_passes_test(logged_in_as_teacher, login_url=reverse_lazy('login_new'))
 def organisation_manage(request):
     return organisation_create(request)
+
+
+@login_required(login_url=reverse_lazy('login_new'))
+@user_passes_test(logged_in_as_teacher, login_url=reverse_lazy('login_new'))
+def organisation_leave(request):
+    teacher = request.user.new_teacher
+
+    check_teacher_is_not_admin(teacher)
+
+    if request.method == 'POST':
+        classes = Class.objects.filter(teacher=teacher)
+        for klass in classes:
+            teacher_id = request.POST.get(klass.access_code, None)
+            if teacher_id:
+                new_teacher = get_object_or_404(Teacher, id=teacher_id)
+                klass.teacher = new_teacher
+                klass.save()
+
+        classes = Class.objects.filter(teacher=teacher)
+        teachers = Teacher.objects.filter(school=teacher.school).exclude(id=teacher.id)
+
+        if classes.exists():
+            messages.info(request, 'You still have classes, you must first move them to another teacher within your school or club.')
+            return render(request, 'redesign/teach_new/teacher_move_all_classes_new.html', {
+                'original_teacher': teacher,
+                'classes': classes,
+                'teachers': teachers,
+                'submit_button_text': 'Move classes and leave',
+            })
+
+        teacher.school = None
+        teacher.save()
+
+        messages.success(request, 'You have successfully left the school or club.')
+
+        return HttpResponseRedirect(reverse_lazy('onboarding-organisation'))
+
+
+def check_teacher_is_not_admin(teacher):
+    if teacher.is_admin:
+        raise Http404
diff --git a/portal/views/teacher/dashboard.py b/portal/views/teacher/dashboard.py
--- a/portal/views/teacher/dashboard.py
+++ b/portal/views/teacher/dashboard.py
@@ -297,7 +297,7 @@ def organisation_kick(request, pk):
 
     if classes.exists():
         messages.info(request, 'This teacher still has classes assigned to them. You must first move them to another teacher in your school or club.')
-        return render(request, 'redesign/teach/teacher_move_all_classes.html', {
+        return render(request, 'redesign/teach_new/teacher_move_all_classes_new.html', {
             'original_teacher': teacher,
             'classes': classes,
             'teachers': teachers,


Test patch:
diff --git a/portal/tests/pageObjects/portal/teach/dashboard_page_new.py b/portal/tests/pageObjects/portal/teach/dashboard_page_new.py
--- a/portal/tests/pageObjects/portal/teach/dashboard_page_new.py
+++ b/portal/tests/pageObjects/portal/teach/dashboard_page_new.py
@@ -36,6 +36,8 @@
 # identified as the original program.
 from teach_base_page_new import TeachBasePage
 from class_page_new import TeachClassPage
+from move_classes_page_new import TeachMoveClassesPage
+from onboarding_organisation_page import OnboardingOrganisationPage
 from selenium.webdriver.support.ui import Select
 
 import time
@@ -163,17 +165,6 @@ def does_class_exist(self, name, access_code):
     def is_teacher_non_admin(self):
         return "Make admin" in self.browser.find_element_by_id('teachers_table').text
 
-    def have_classes(self):
-        return self.element_exists_by_id('classes_table')
-
-    def does_not_have_classes(self):
-        return self.element_does_not_exist_by_id('classes_table')
-
-    def does_class_exist(self, name, access_code):
-        return self.have_classes() and \
-               (name in self.browser.find_element_by_id('classes_table').text) and \
-               (access_code in self.browser.find_element_by_id('classes_table').text)
-
     def click_kick_button(self):
         self.browser.find_element_by_id('kick_button').click()
 
@@ -189,6 +180,14 @@ def click_make_non_admin_button(self):
 
         return self
 
+    def leave_organisation_with_students(self):
+        self._click_leave_button()
+
+        return TeachMoveClassesPage(self.browser)
+
+    def _click_leave_button(self):
+        self.browser.find_element_by_id('leave_organisation_button').click()
+
     def is_dialog_showing(self):
         return self.browser.find_element_by_xpath("//div[contains(@class,'ui-dialog')]").is_displayed()
 
@@ -197,5 +196,10 @@ def confirm_dialog(self):
 
         return self
 
+    def confirm_kick_with_students_dialog(self):
+        self._click_confirm()
+
+        return TeachMoveClassesPage(self.browser)
+
     def _click_confirm(self):
         self.browser.find_element_by_xpath("//button[contains(text(),'Confirm')]").click()
diff --git a/portal/tests/pageObjects/portal/teach/move_classes_page_new.py b/portal/tests/pageObjects/portal/teach/move_classes_page_new.py
new file mode 100644
--- /dev/null
+++ b/portal/tests/pageObjects/portal/teach/move_classes_page_new.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+# Code for Life
+#
+# Copyright (C) 2016, Ocado Innovation Limited
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# ADDITIONAL TERMS – Section 7 GNU General Public Licence
+#
+# This licence does not grant any right, title or interest in any “Ocado” logos,
+# trade names or the trademark “Ocado” or any other trademarks or domain names
+# owned by Ocado Innovation Limited or the Ocado group of companies or any other
+# distinctive brand features of “Ocado” as may be secured from time to time. You
+# must not distribute any modification of this program using the trademark
+# “Ocado” or claim any affiliation or association with Ocado or its employees.
+#
+# You are not authorised to use the name Ocado (or any of its trade names) or
+# the names of any author or contributor in advertising or for publicity purposes
+# pertaining to the distribution of this program, without the prior written
+# authorisation of Ocado.
+#
+# Any propagation, distribution or conveyance of this program must include this
+# copyright notice and these terms. You must not misrepresent the origins of this
+# program; modified versions of the program must be marked as such and not
+# identified as the original program.
+import dashboard_page_new
+import onboarding_organisation_page
+
+from teach_base_page_new import TeachBasePage
+
+
+class TeachMoveClassesPage(TeachBasePage):
+    def __init__(self, browser):
+        super(TeachMoveClassesPage, self).__init__(browser)
+
+        assert self.on_correct_page('move_all_classes_page')
+
+    def move_and_kick(self):
+        self.browser.find_element_by_id('move_classes_button').click()
+        return dashboard_page_new.TeachDashboardPage(self.browser)
+
+    def move_and_leave(self):
+        self.browser.find_element_by_id('move_classes_button').click()
+        return onboarding_organisation_page.OnboardingOrganisationPage(self.browser)
diff --git a/portal/tests/test_organisation_new.py b/portal/tests/test_organisation_new.py
--- a/portal/tests/test_organisation_new.py
+++ b/portal/tests/test_organisation_new.py
@@ -213,6 +213,98 @@ def test_kick(self):
 
         assert page.is_not_teacher_in_school(new_last_name)
 
+    def test_move_classes_and_kick(self):
+        email_1, password_1 = signup_teacher_directly()
+        name, postcode = create_organisation_directly(email_1)
+        _, class_name_1, access_code_1 = create_class_directly(email_1)
+        create_school_student_directly(access_code_1)
+
+        title, first_name, last_name, email_2, password_2 = generate_details()
+
+        new_last_name = "New Teacher"
+
+        selenium.get(self.live_server_url + "/portal/redesign/home")
+        page = HomePage(selenium) \
+            .go_to_signup_page() \
+            .signup(title, first_name, new_last_name, email_2, password_2, password_2)
+
+        page = email_utils.follow_verify_email_link_to_onboarding(page, mail.outbox[0])
+        mail.outbox = []
+
+        page = page.login_no_school(email_2, password_2).join_organisation(name)
+
+        page = page \
+            .logout() \
+            .go_to_login_page() \
+            .login(email_1, password_1)
+
+        page = page.accept_join_request()
+
+        assert page.is_teacher_in_school(new_last_name)
+
+        _, class_name_2, access_code_2 = create_class_directly(email_2)
+        create_school_student_directly(access_code_2)
+
+        page = page.click_kick_button().confirm_kick_with_students_dialog()
+
+        assert page.__class__.__name__ == 'TeachMoveClassesPage'
+
+        page = page.move_and_kick()
+
+        assert page.is_not_teacher_in_school(new_last_name)
+
+    def test_leave_organisation(self):
+        email_1, password_1 = signup_teacher_directly()
+        name, postcode = create_organisation_directly(email_1)
+        _, class_name_1, access_code_1 = create_class_directly(email_1)
+        create_school_student_directly(access_code_1)
+
+        title, first_name, last_name, email_2, password_2 = generate_details()
+
+        new_last_name = "New Teacher"
+
+        selenium.get(self.live_server_url + "/portal/redesign/home")
+        page = HomePage(selenium) \
+            .go_to_signup_page() \
+            .signup(title, first_name, new_last_name, email_2, password_2, password_2)
+
+        page = email_utils.follow_verify_email_link_to_onboarding(page, mail.outbox[0])
+        mail.outbox = []
+
+        page = page.login_no_school(email_2, password_2).join_organisation(name)
+
+        page = page \
+            .logout() \
+            .go_to_login_page() \
+            .login(email_1, password_1)
+
+        page = page.accept_join_request()
+
+        assert page.is_teacher_in_school(new_last_name)
+
+        _, class_name_2, access_code_2 = create_class_directly(email_2)
+        create_school_student_directly(access_code_2)
+
+        page = page \
+            .logout() \
+            .go_to_login_page() \
+            .login(email_2, password_2)
+
+        page = page.leave_organisation_with_students()
+
+        assert page.__class__.__name__ == 'TeachMoveClassesPage'
+
+        page = page.move_and_leave()
+
+        assert page.__class__.__name__ == 'OnboardingOrganisationPage'
+
+        page = page \
+            .logout() \
+            .go_to_login_page() \
+            .login(email_1, password_1)
+
+        assert page.is_not_teacher_in_school(new_last_name)
+
     def test_toggle_admin(self):
         email_1, password_1 = signup_teacher_directly()
         email_2, password_2 = signup_teacher_directly()