Repository: carpentries/amy

Issue:
Show events with missing instructors
…in the workshop issues page.



Discussion:
Taking this a step further - can AMY read the workshop website's repository & get len(instructor) and len(helper), then alert if these do not equal count(instructors) and count(helpers) in the database?

Sometimes I put an event in AMY but the organizers add instructors/helpers afterwards and don't tell me.   This will cue the admins to double check that all instructors/helpers have been entered in.  (Or likewise, someone drops off, we want to delete them from our records)

@maneesha such feature should go to #562 I think.


Resolving patch:
diff --git a/workshops/views.py b/workshops/views.py
--- a/workshops/views.py
+++ b/workshops/views.py
@@ -1684,13 +1684,25 @@ def all_activity_over_time(request):
 def workshop_issues(request):
     '''Display workshops in the database whose records need attention.'''
 
-    events = Event.objects.active().past_events().filter(
+    events = Event.objects.active().past_events().annotate(
+        num_instructors=Count(
+            Case(
+                When(
+                    task__role__name='instructor',
+                    then=Value(1)
+                ),
+                output_field=IntegerField()
+            )
+        )
+    )
+    events = events.filter(
         Q(attendance=None) | Q(attendance=0) |
         Q(country=None) |
         Q(venue=None) | Q(venue__exact='') |
         Q(address=None) | Q(address__exact='') |
         Q(latitude=None) | Q(longitude=None) |
-        Q(start__gt=F('end'))
+        Q(start__gt=F('end')) |
+        Q(num_instructors=0)
     )
 
     assigned_to, is_admin = assignment_selection(request)
@@ -1712,6 +1724,7 @@ def workshop_issues(request):
             not e.longitude
         )
         e.bad_dates_ = e.start and e.end and (e.start > e.end)
+        e.no_instructors_ = not e.num_instructors
 
     context = {
         'title': 'Workshops with Issues',


Test patch:
diff --git a/workshops/test/test_issues.py b/workshops/test/test_issues.py
new file mode 100644
--- /dev/null
+++ b/workshops/test/test_issues.py
@@ -0,0 +1,129 @@
+from datetime import timedelta, date
+
+from django.core.urlresolvers import reverse
+
+from ..models import Event, Host, Role, Person
+from .base import TestBase
+
+
+class TestIssuesViews(TestBase):
+    def setUp(self):
+        super()._setUpUsersAndLogin()
+        super()._setUpRoles()
+
+        self.url = reverse('workshop_issues')
+        self.today = date.today()
+        oneday = timedelta(days=1)
+        self.weekago = self.today - 7 * oneday
+        self.yesterday = self.today - oneday
+        self.tomorrow = self.today + oneday
+        self.instructor_role = Role.objects.get(name='instructor')
+
+    def test_workshop_issues_past(self):
+        """Test if workshop issues collects events from past only."""
+        past = Event.objects.create(
+            slug='event-in-past', start=self.yesterday,
+            host=Host.objects.first())
+        future = Event.objects.create(
+            slug='event-in-future', start=self.tomorrow,
+            host=Host.objects.first())
+
+        rv = self.client.get(self.url)
+        self.assertIn(past, rv.context['events'])
+        self.assertNotIn(future, rv.context['events'])
+
+    def test_workshop_issues_active(self):
+        """Test if workshop issues collects active events only."""
+        inactive = Event.objects.create(
+            slug='inactive-event', start=self.yesterday,
+            completed=True, host=Host.objects.first())
+        active = Event.objects.create(
+            slug='active-event', start=self.yesterday,
+            completed=False, host=Host.objects.first())
+
+        rv = self.client.get(self.url)
+        self.assertNotIn(inactive, rv.context['events'])
+        self.assertIn(active, rv.context['events'])
+
+    def test_workshop_issues_no_attendance(self):
+        """Test if workshop issues collects events without attendance
+        figures."""
+        attendance = Event.objects.create(
+            slug='event-with-attendance',
+            start=self.weekago, end=self.yesterday,
+            attendance=36, host=Host.objects.first(),
+            country='US', address='A', venue='B', latitude=89, longitude=179)
+        attendance.task_set.create(person=Person.objects.first(),
+                                   role=self.instructor_role)
+        no_attendance = Event.objects.create(
+            slug='event-with-no-attendance',
+            start=self.weekago, end=self.yesterday,
+            attendance=0, host=Host.objects.first(),
+            country='US', address='A', venue='B', latitude=89, longitude=179)
+        no_attendance.task_set.create(person=Person.objects.first(),
+                                      role=self.instructor_role)
+
+        rv = self.client.get(self.url)
+        self.assertNotIn(attendance, rv.context['events'])
+        self.assertIn(no_attendance, rv.context['events'])
+
+    def test_workshop_issues_no_location(self):
+        """Test if workshop issues collects events without location."""
+        location = Event.objects.create(
+            slug='event-with-location',
+            start=self.weekago, end=self.yesterday,
+            attendance=36, host=Host.objects.first(),
+            country='US', address='A', venue='B', latitude=89, longitude=179)
+        location.task_set.create(person=Person.objects.first(),
+                                 role=self.instructor_role)
+        no_location = Event.objects.create(
+            slug='event-with-no-location',
+            start=self.weekago, end=self.yesterday,
+            attendance=36, host=Host.objects.first(),
+            country='US', address='', venue='B', latitude=89, longitude=179)
+        no_location.task_set.create(person=Person.objects.first(),
+                                    role=self.instructor_role)
+
+        rv = self.client.get(self.url)
+        self.assertNotIn(location, rv.context['events'])
+        self.assertIn(no_location, rv.context['events'])
+
+    def test_workshop_issues_wrong_dates(self):
+        """Test if workshop issues collects events with invalid dates."""
+        okay = Event.objects.create(
+            slug='event-with-okay-dates',
+            start=self.weekago, end=self.yesterday,
+            attendance=36, host=Host.objects.first(),
+            country='US', address='A', venue='B', latitude=89, longitude=179)
+        okay.task_set.create(person=Person.objects.first(),
+                             role=self.instructor_role)
+        invalid_dates = Event.objects.create(
+            slug='event-with-invalid-dates',
+            start=self.yesterday, end=self.weekago,
+            attendance=36, host=Host.objects.first(),
+            country='US', address='A', venue='B', latitude=89, longitude=179)
+        invalid_dates.task_set.create(person=Person.objects.first(),
+                                      role=self.instructor_role)
+
+        rv = self.client.get(self.url)
+        self.assertNotIn(okay, rv.context['events'])
+        self.assertIn(invalid_dates, rv.context['events'])
+
+    def test_workshop_issues_no_instructors(self):
+        """Test if workshop issues collects events without instructors."""
+        instructor = Event.objects.create(
+            slug='event-with-instructor',
+            start=self.weekago, end=self.yesterday,
+            attendance=36, host=Host.objects.first(),
+            country='US', address='A', venue='B', latitude=89, longitude=179)
+        instructor.task_set.create(person=Person.objects.first(),
+                                   role=self.instructor_role)
+        no_instructors = Event.objects.create(
+            slug='event-with-no-instructors',
+            start=self.weekago, end=self.yesterday,
+            attendance=36, host=Host.objects.first(),
+            country='US', address='A', venue='B', latitude=89, longitude=179)
+
+        rv = self.client.get(self.url)
+        self.assertNotIn(instructor, rv.context['events'])
+        self.assertIn(no_instructors, rv.context['events'])