Repository: apluslms/a-plus

Issue:
Email course news content to the course staff (in addition to students)
When the teacher creates a news item and enables emailing it to the the students, it should also be possible to email the same message to the course staff.

There could be an additional checkbox in the news form for emailing the staff too.

News email is implemented here:
* https://github.com/apluslms/a-plus/blob/b36e145cbb91b5a3b47204c122b081571d1bcee8/lib/email_messages.py#L67
* https://github.com/apluslms/a-plus/blob/b36e145cbb91b5a3b47204c122b081571d1bcee8/news/forms.py#L11
* https://github.com/apluslms/a-plus/blob/b36e145cbb91b5a3b47204c122b081571d1bcee8/news/views.py#L56

I suppose the recipients could be combined with SQL UNION like this: 
`recipients = course_instance.students.union(course_instance.course_staff)`


Discussion:
The feature request came from a teacher: internal ticket
https://rt.cs.aalto.fi/Ticket/Display.html?id=23299

Resolving patch:
diff --git a/lib/email_messages.py b/lib/email_messages.py
--- a/lib/email_messages.py
+++ b/lib/email_messages.py
@@ -5,6 +5,7 @@
 
 from .helpers import Enum, build_aplus_url
 from course.models import CourseInstance
+from userprofile.models import UserProfile
 
 
 logger = logging.getLogger('aplus.lib.email_messages')
@@ -64,24 +65,27 @@ def email_course_error(request, exercise, message, exception=True):
         pass
 
 
-def email_course_students(
+def email_course_students_and_staff(
         instance: CourseInstance,
         subject: str,
         message: str,
-        audience: Enum = CourseInstance.ENROLLMENT_AUDIENCE.ALL_USERS,
+        student_audience: Enum = CourseInstance.ENROLLMENT_AUDIENCE.ALL_USERS,
+        include_staff: bool = False,
         ) -> int:
     """
-    Sends an email to students on the course. Audience parameter controls whether the mail goes
-    to all (default), just internal, or just external students.
+    Sends an email to students and staff of the course. Audience parameter controls whether the mail goes
+    to all, just internal, or just external students.
     Returns number of emails sent, or -1 in case of error.
     """
-    students = instance.students
-    if audience == CourseInstance.ENROLLMENT_AUDIENCE.INTERNAL_USERS:
-        students = students.filter(organization=settings.LOCAL_ORGANIZATION)
-    elif audience == CourseInstance.ENROLLMENT_AUDIENCE.EXTERNAL_USERS:
-        students = students.exclude(organization=settings.LOCAL_ORGANIZATION)
+    student_querys = {
+        CourseInstance.ENROLLMENT_AUDIENCE.ALL_USERS: instance.students,
+        CourseInstance.ENROLLMENT_AUDIENCE.INTERNAL_USERS: instance.students.filter(organization=settings.LOCAL_ORGANIZATION),
+        CourseInstance.ENROLLMENT_AUDIENCE.EXTERNAL_USERS: instance.students.exclude(organization=settings.LOCAL_ORGANIZATION),
+    }
+    recipients = student_querys.get(student_audience, UserProfile.objects.none()).exclude(user__email='').values_list("user__email", flat=True)
+    if include_staff:
+        recipients = recipients.union(instance.course_staff.exclude(user__email='').values_list("user__email", flat=True))
 
-    recipients = students.exclude(user__email='').values_list("user__email", flat=True)
     emails = tuple(map(lambda x: (subject, message, settings.SERVER_EMAIL, [x]), recipients))
 
     try:
diff --git a/news/forms.py b/news/forms.py
--- a/news/forms.py
+++ b/news/forms.py
@@ -6,10 +6,14 @@
 
 class NewsForm(forms.ModelForm):
 
-    email = forms.BooleanField(
+    email_students = forms.BooleanField(
         required=False,
         label=_("SEND_EMAIL_TO_STUDENTS"),
     )
+    email_staff = forms.BooleanField(
+        required=False,
+        label=_("SEND_EMAIL_TO_STAFF"),
+    )
 
     class Meta:
         model = News
@@ -17,7 +21,8 @@ class Meta:
             'audience',
             'publish',
             'pin',
-            'email',
+            'email_students',
+            'email_staff',
             'title',
             'body',
         ]
diff --git a/news/views.py b/news/views.py
--- a/news/views.py
+++ b/news/views.py
@@ -6,7 +6,7 @@
 from authorization.permissions import ACCESS
 from course.viewbase import CourseInstanceBaseView, CourseInstanceMixin
 from lib.viewbase import BaseFormView, BaseRedirectView
-from lib.email_messages import email_course_students
+from lib.email_messages import email_course_students_and_staff
 from .forms import NewsForm
 from .models import News
 
@@ -49,13 +49,15 @@ def get_success_url(self):
 
     def form_valid(self, form):
         form.save()
-        if form.cleaned_data['email']:
+        if form.cleaned_data['email_students'] or form.cleaned_data['email_staff']:
             subject = f"[{settings.BRAND_NAME} course news] {self.instance.course.code}: {self.news_item.title}"
-            if email_course_students(
+            student_audience = self.news_item.audience if form.cleaned_data['email_students'] else None
+            if email_course_students_and_staff(
                 self.instance,
                 subject,
                 self.news_item.body,
-                self.news_item.audience,
+                student_audience,
+                form.cleaned_data['email_staff'],
             ) < 0:
                 messages.error(self.request, _('FAILED_TO_SEND_EMAIL'))
         return super().form_valid(form)


Test patch: