Repository: carpentries/amy

Issue:
Trainees eligible for certification are not showing up in list
When filtering the Trainees list - in the "Is SWC/DC instructor?" box, you can select "No, but eligible to be certified". This should bring up any trainees who have completed all checkout steps but have not yet been awarded a certificate. However, the matching parameters appear to be set up incorrectly, as not all eligible trainees are shown. 

As an example, just now I applied this filter and showed up no trainees waiting to be certified: 
![screen shot 2018-02-07 at 9 48 54 am](https://user-images.githubusercontent.com/19176319/35932472-8829e7ca-0bec-11e8-8230-123532fcf398.png)

However, looking at some of the individual training events, I found at least one who is eligible but has not been certified. 
![35932487-94a6a8d0-0bec-11e8-8a56-cf14eaba54ba](https://user-images.githubusercontent.com/72821/39673067-d7ce1952-5135-11e8-8b25-f439e42d0ae2.png)


I believe the issue in this case is that the trainee had completed the homework requirement twice, however in other cases, there are trainees who have done multiple discussion sessions, or done the steps out of order, or done SWC Homework but DC Demo, or vice versa. The matching criteria need to be updated to fit all of these situations. 

The matching criteria need to be:
IN ANY ORDER
(Discussion)(1 or more) AND
((SWC Demo)(1 or more) OR (DC Demo)(1 or more)) AND 
((SWC Homework)(1 or more) OR (DC Demo)(1 or more))





Discussion:

Resolving patch:
diff --git a/workshops/filters.py b/workshops/filters.py
--- a/workshops/filters.py
+++ b/workshops/filters.py
@@ -294,8 +294,13 @@ def filter_trainees_by_instructor_status(queryset, name, choice):
     elif choice == 'dc':
         return queryset.filter(is_dc_instructor=True)
     elif choice == 'eligible':
-        return queryset.filter(Q(swc_eligible=True, is_swc_instructor=False) |
-                               Q(dc_eligible=True, is_dc_instructor=False))
+        # Instructor eligible but without any badge.
+        # This code is kept in Q()-expressions to allow for fast condition
+        # change.
+        return queryset.filter(
+            Q(instructor_eligible=True) &
+            (Q(is_swc_instructor=False) & Q(is_dc_instructor=False))
+        )
     else:  # choice == 'no'
         return queryset.filter(is_swc_instructor=False, is_dc_instructor=False)
 
diff --git a/workshops/models.py b/workshops/models.py
--- a/workshops/models.py
+++ b/workshops/models.py
@@ -332,14 +332,16 @@ def passed(requirement):
             passed_swc_demo=passed('SWC Demo'),
             passed_dc_demo=passed('DC Demo'),
         ).annotate(
-            swc_eligible=(F('passed_training') *
-                          F('passed_swc_homework') *
-                          F('passed_discussion') *
-                          F('passed_swc_demo')),
-            dc_eligible=(F('passed_training') *
-                         F('passed_dc_homework') *
-                         F('passed_discussion') *
-                         F('passed_dc_demo')),
+            # We're using Maths to calculate "binary" score for a person to
+            # be instructor badge eligible. Legend:
+            # * means "AND"
+            # + means "OR"
+            instructor_eligible=(
+                F('passed_training') *
+                (F('passed_swc_homework') + F('passed_dc_homework')) *
+                F('passed_discussion') *
+                (F('passed_swc_demo') + F('passed_dc_demo'))
+            )
         )
 
 
diff --git a/workshops/templatetags/training_progress.py b/workshops/templatetags/training_progress.py
--- a/workshops/templatetags/training_progress.py
+++ b/workshops/templatetags/training_progress.py
@@ -1,4 +1,5 @@
 from django import template
+from django.template.defaultfilters import escape
 from django.utils.safestring import mark_safe
 
 from workshops.models import TrainingProgress
@@ -37,7 +38,7 @@ def progress_description(progress):
                          progress.evaluated_by.full_name)
                       if progress.evaluated_by is not None else 'submitted'),
         day=progress.created_at.strftime('%A %d %B %Y at %H:%M'),
-        notes='<br />Notes: {}'.format(progress.notes) if progress.notes else '',
+        notes='<br />Notes: {}'.format(escape(progress.notes)) if progress.notes else '',
     )
     text = text[0].upper() + text[1:]
     return mark_safe(text)


Test patch: