We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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)`

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `news/forms.py`
Content:
```
1 from django import forms
2 from django.utils.translation import gettext_lazy as _
3 
4 from .models import News
5 
6 
7 class NewsForm(forms.ModelForm):
8 
9     email = forms.BooleanField(
10         required=False,
11         label=_("SEND_EMAIL_TO_STUDENTS"),
12     )
13 
14     class Meta:
15         model = News
16         fields = [
17             'audience',
18             'publish',
19             'pin',
20             'email',
21             'title',
22             'body',
23         ]
24
```

Path: `lib/email_messages.py`
Content:
```
1 import logging
2 import traceback
3 from django.conf import settings
4 from django.core.mail import send_mail, send_mass_mail
5 
6 from .helpers import Enum, build_aplus_url
7 from course.models import CourseInstance
8 
9 
10 logger = logging.getLogger('aplus.lib.email_messages')
11 
12 
13 def email_course_instance(instance, subject, message, everyone=False) -> bool:
14     """
15     Sends an email to a course instance's technical support emails or teachers if technical support not set.
16     If everyone == True, sends emails to teachers anyway.
17     """
18     recipients = []
19     if instance.technical_error_emails:
20         recipients = instance.technical_error_emails.split(",")
21     if everyone or not recipients:
22         recipients = instance.teachers.exclude(user__email='').values_list("user__email", flat=True)
23 
24     if not recipients:
25         raise ValueError("No recipients")
26 
27     try:
28         return send_mail(subject, message, settings.SERVER_EMAIL, recipients, True) == 1
29     except:
30         logger.exception('Failed to send course instance emails.')
31         raise
32 
33 
34 def email_course_error(request, exercise, message, exception=True):
35     """
36     Sends error message to course instance's teachers or technical support emails if set.
37     """
38     instance = exercise.course_instance
39 
40     error_trace = "-"
41     if exception:
42         error_trace = traceback.format_exc()
43 
44     if request:
45         request_fields = repr(request)
46     else:
47         request_fields = "No request available"
48 
49     subject = settings.EXERCISE_ERROR_SUBJECT.format(
50         course=instance.course.code,
51         exercise=str(exercise))
52     body = settings.EXERCISE_ERROR_DESCRIPTION.format(
53         message=message,
54         exercise_url=build_aplus_url(
55             exercise.get_absolute_url(), user_url=True),
56         course_edit_url=build_aplus_url(
57             instance.get_url('course-details'), user_url=True),
58         error_trace=error_trace,
59         request_fields=request_fields)
60 
61     try:
62         email_course_instance(instance, subject, body)
63     except: # pylint: disable=bare-except
64         pass
65 
66 
67 def email_course_students(
68         instance: CourseInstance,
69         subject: str,
70         message: str,
71         audience: Enum = CourseInstance.ENROLLMENT_AUDIENCE.ALL_USERS,
72         ) -> int:
73     """
74     Sends an email to students on the course. Audience parameter controls whether the mail goes
75     to all (default), just internal, or just external students.
76     Returns number of emails sent, or -1 in case of error.
77     """
78     students = instance.students
79     if audience == CourseInstance.ENROLLMENT_AUDIENCE.INTERNAL_USERS:
80         students = students.filter(organization=settings.LOCAL_ORGANIZATION)
81     elif audience == CourseInstance.ENROLLMENT_AUDIENCE.EXTERNAL_USERS:
82         students = students.exclude(organization=settings.LOCAL_ORGANIZATION)
83 
84     recipients = students.exclude(user__email='').values_list("user__email", flat=True)
85     emails = tuple(map(lambda x: (subject, message, settings.SERVER_EMAIL, [x]), recipients))
86 
87     try:
88         return send_mass_mail(emails)
89     except: # pylint: disable=bare-except
90         logger.exception('Failed to send course instance emails.')
91         return -1
92
```

Path: `news/views.py`
Content:
```
1 from django.conf import settings
2 from django.contrib import messages
3 from django.shortcuts import get_object_or_404
4 from django.utils.translation import gettext_lazy as _
5 
6 from authorization.permissions import ACCESS
7 from course.viewbase import CourseInstanceBaseView, CourseInstanceMixin
8 from lib.viewbase import BaseFormView, BaseRedirectView
9 from lib.email_messages import email_course_students
10 from .forms import NewsForm
11 from .models import News
12 
13 
14 class ListNewsView(CourseInstanceBaseView):
15     access_mode = ACCESS.TEACHER
16     template_name = "news/list.html"
17 
18     def get_common_objects(self):
19         super().get_common_objects()
20         self.news = self.instance.news.all()
21         self.note("news")
22 
23 
24 class EditNewsView(CourseInstanceMixin, BaseFormView):
25     access_mode = ACCESS.TEACHER
26     template_name = "news/edit.html"
27     form_class = NewsForm
28     news_item_kw = "news_id"
29 
30     def get_form_kwargs(self):
31         kwargs = super().get_form_kwargs()
32 
33         news_id = self._get_kwarg(self.news_item_kw, default=None)
34         if news_id:
35             self.news_item = get_object_or_404(
36                 News,
37                 pk=news_id,
38                 course_instance=self.instance
39             )
40             self.note("news_item")
41         else:
42             self.news_item = News(course_instance=self.instance)
43 
44         kwargs["instance"] = self.news_item
45         return kwargs
46 
47     def get_success_url(self):
48         return self.instance.get_url("news-list")
49 
50     def form_valid(self, form):
51         form.save()
52         if form.cleaned_data['email']:
53             subject = f"[{settings.BRAND_NAME} course news] {self.instance.course.code}: {self.news_item.title}"
54             if email_course_students(
55                 self.instance,
56                 subject,
57                 self.news_item.body,
58                 self.news_item.audience,
59             ) < 0:
60                 messages.error(self.request, _('FAILED_TO_SEND_EMAIL'))
61         return super().form_valid(form)
62 
63 
64 class RemoveNewsView(CourseInstanceMixin, BaseRedirectView):
65     access_mode = ACCESS.TEACHER
66     news_item_kw = "news_id"
67 
68     def get_resource_objects(self):
69         super().get_resource_objects()
70         self.news_item = get_object_or_404(
71             News,
72             id=self._get_kwarg(self.news_item_kw),
73             course_instance=self.instance,
74         )
75         self.note("news_item")
76 
77     def post(self, request, *args, **kwargs):
78         self.news_item.delete()
79         return self.redirect(self.instance.get_url("news-list"))
80
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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 @@
         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 @@
             '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 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)

{"golden_diff": "diff --git a/lib/email_messages.py b/lib/email_messages.py\n--- a/lib/email_messages.py\n+++ b/lib/email_messages.py\n@@ -5,6 +5,7 @@\n \n from .helpers import Enum, build_aplus_url\n from course.models import CourseInstance\n+from userprofile.models import UserProfile\n \n \n logger = logging.getLogger('aplus.lib.email_messages')\n@@ -64,24 +65,27 @@\n         pass\n \n \n-def email_course_students(\n+def email_course_students_and_staff(\n         instance: CourseInstance,\n         subject: str,\n         message: str,\n-        audience: Enum = CourseInstance.ENROLLMENT_AUDIENCE.ALL_USERS,\n+        student_audience: Enum = CourseInstance.ENROLLMENT_AUDIENCE.ALL_USERS,\n+        include_staff: bool = False,\n         ) -> int:\n     \"\"\"\n-    Sends an email to students on the course. Audience parameter controls whether the mail goes\n-    to all (default), just internal, or just external students.\n+    Sends an email to students and staff of the course. Audience parameter controls whether the mail goes\n+    to all, just internal, or just external students.\n     Returns number of emails sent, or -1 in case of error.\n     \"\"\"\n-    students = instance.students\n-    if audience == CourseInstance.ENROLLMENT_AUDIENCE.INTERNAL_USERS:\n-        students = students.filter(organization=settings.LOCAL_ORGANIZATION)\n-    elif audience == CourseInstance.ENROLLMENT_AUDIENCE.EXTERNAL_USERS:\n-        students = students.exclude(organization=settings.LOCAL_ORGANIZATION)\n+    student_querys = {\n+        CourseInstance.ENROLLMENT_AUDIENCE.ALL_USERS: instance.students,\n+        CourseInstance.ENROLLMENT_AUDIENCE.INTERNAL_USERS: instance.students.filter(organization=settings.LOCAL_ORGANIZATION),\n+        CourseInstance.ENROLLMENT_AUDIENCE.EXTERNAL_USERS: instance.students.exclude(organization=settings.LOCAL_ORGANIZATION),\n+    }\n+    recipients = student_querys.get(student_audience, UserProfile.objects.none()).exclude(user__email='').values_list(\"user__email\", flat=True)\n+    if include_staff:\n+        recipients = recipients.union(instance.course_staff.exclude(user__email='').values_list(\"user__email\", flat=True))\n \n-    recipients = students.exclude(user__email='').values_list(\"user__email\", flat=True)\n     emails = tuple(map(lambda x: (subject, message, settings.SERVER_EMAIL, [x]), recipients))\n \n     try:\ndiff --git a/news/forms.py b/news/forms.py\n--- a/news/forms.py\n+++ b/news/forms.py\n@@ -6,10 +6,14 @@\n \n class NewsForm(forms.ModelForm):\n \n-    email = forms.BooleanField(\n+    email_students = forms.BooleanField(\n         required=False,\n         label=_(\"SEND_EMAIL_TO_STUDENTS\"),\n     )\n+    email_staff = forms.BooleanField(\n+        required=False,\n+        label=_(\"SEND_EMAIL_TO_STAFF\"),\n+    )\n \n     class Meta:\n         model = News\n@@ -17,7 +21,8 @@\n             'audience',\n             'publish',\n             'pin',\n-            'email',\n+            'email_students',\n+            'email_staff',\n             'title',\n             'body',\n         ]\ndiff --git a/news/views.py b/news/views.py\n--- a/news/views.py\n+++ b/news/views.py\n@@ -6,7 +6,7 @@\n from authorization.permissions import ACCESS\n from course.viewbase import CourseInstanceBaseView, CourseInstanceMixin\n from lib.viewbase import BaseFormView, BaseRedirectView\n-from lib.email_messages import email_course_students\n+from lib.email_messages import email_course_students_and_staff\n from .forms import NewsForm\n from .models import News\n \n@@ -49,13 +49,15 @@\n \n     def form_valid(self, form):\n         form.save()\n-        if form.cleaned_data['email']:\n+        if form.cleaned_data['email_students'] or form.cleaned_data['email_staff']:\n             subject = f\"[{settings.BRAND_NAME} course news] {self.instance.course.code}: {self.news_item.title}\"\n-            if email_course_students(\n+            student_audience = self.news_item.audience if form.cleaned_data['email_students'] else None\n+            if email_course_students_and_staff(\n                 self.instance,\n                 subject,\n                 self.news_item.body,\n-                self.news_item.audience,\n+                student_audience,\n+                form.cleaned_data['email_staff'],\n             ) < 0:\n                 messages.error(self.request, _('FAILED_TO_SEND_EMAIL'))\n         return super().form_valid(form)\n", "issue": "Email course news content to the course staff (in addition to students)\nWhen 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.\r\n\r\nThere could be an additional checkbox in the news form for emailing the staff too.\r\n\r\nNews email is implemented here:\r\n* https://github.com/apluslms/a-plus/blob/b36e145cbb91b5a3b47204c122b081571d1bcee8/lib/email_messages.py#L67\r\n* https://github.com/apluslms/a-plus/blob/b36e145cbb91b5a3b47204c122b081571d1bcee8/news/forms.py#L11\r\n* https://github.com/apluslms/a-plus/blob/b36e145cbb91b5a3b47204c122b081571d1bcee8/news/views.py#L56\r\n\r\nI suppose the recipients could be combined with SQL UNION like this: \r\n`recipients = course_instance.students.union(course_instance.course_staff)`\n", "before_files": [{"content": "from django import forms\nfrom django.utils.translation import gettext_lazy as _\n\nfrom .models import News\n\n\nclass NewsForm(forms.ModelForm):\n\n    email = forms.BooleanField(\n        required=False,\n        label=_(\"SEND_EMAIL_TO_STUDENTS\"),\n    )\n\n    class Meta:\n        model = News\n        fields = [\n            'audience',\n            'publish',\n            'pin',\n            'email',\n            'title',\n            'body',\n        ]\n", "path": "news/forms.py"}, {"content": "import logging\nimport traceback\nfrom django.conf import settings\nfrom django.core.mail import send_mail, send_mass_mail\n\nfrom .helpers import Enum, build_aplus_url\nfrom course.models import CourseInstance\n\n\nlogger = logging.getLogger('aplus.lib.email_messages')\n\n\ndef email_course_instance(instance, subject, message, everyone=False) -> bool:\n    \"\"\"\n    Sends an email to a course instance's technical support emails or teachers if technical support not set.\n    If everyone == True, sends emails to teachers anyway.\n    \"\"\"\n    recipients = []\n    if instance.technical_error_emails:\n        recipients = instance.technical_error_emails.split(\",\")\n    if everyone or not recipients:\n        recipients = instance.teachers.exclude(user__email='').values_list(\"user__email\", flat=True)\n\n    if not recipients:\n        raise ValueError(\"No recipients\")\n\n    try:\n        return send_mail(subject, message, settings.SERVER_EMAIL, recipients, True) == 1\n    except:\n        logger.exception('Failed to send course instance emails.')\n        raise\n\n\ndef email_course_error(request, exercise, message, exception=True):\n    \"\"\"\n    Sends error message to course instance's teachers or technical support emails if set.\n    \"\"\"\n    instance = exercise.course_instance\n\n    error_trace = \"-\"\n    if exception:\n        error_trace = traceback.format_exc()\n\n    if request:\n        request_fields = repr(request)\n    else:\n        request_fields = \"No request available\"\n\n    subject = settings.EXERCISE_ERROR_SUBJECT.format(\n        course=instance.course.code,\n        exercise=str(exercise))\n    body = settings.EXERCISE_ERROR_DESCRIPTION.format(\n        message=message,\n        exercise_url=build_aplus_url(\n            exercise.get_absolute_url(), user_url=True),\n        course_edit_url=build_aplus_url(\n            instance.get_url('course-details'), user_url=True),\n        error_trace=error_trace,\n        request_fields=request_fields)\n\n    try:\n        email_course_instance(instance, subject, body)\n    except: # pylint: disable=bare-except\n        pass\n\n\ndef email_course_students(\n        instance: CourseInstance,\n        subject: str,\n        message: str,\n        audience: Enum = CourseInstance.ENROLLMENT_AUDIENCE.ALL_USERS,\n        ) -> int:\n    \"\"\"\n    Sends an email to students on the course. Audience parameter controls whether the mail goes\n    to all (default), just internal, or just external students.\n    Returns number of emails sent, or -1 in case of error.\n    \"\"\"\n    students = instance.students\n    if audience == CourseInstance.ENROLLMENT_AUDIENCE.INTERNAL_USERS:\n        students = students.filter(organization=settings.LOCAL_ORGANIZATION)\n    elif audience == CourseInstance.ENROLLMENT_AUDIENCE.EXTERNAL_USERS:\n        students = students.exclude(organization=settings.LOCAL_ORGANIZATION)\n\n    recipients = students.exclude(user__email='').values_list(\"user__email\", flat=True)\n    emails = tuple(map(lambda x: (subject, message, settings.SERVER_EMAIL, [x]), recipients))\n\n    try:\n        return send_mass_mail(emails)\n    except: # pylint: disable=bare-except\n        logger.exception('Failed to send course instance emails.')\n        return -1\n", "path": "lib/email_messages.py"}, {"content": "from django.conf import settings\nfrom django.contrib import messages\nfrom django.shortcuts import get_object_or_404\nfrom django.utils.translation import gettext_lazy as _\n\nfrom authorization.permissions import ACCESS\nfrom course.viewbase import CourseInstanceBaseView, CourseInstanceMixin\nfrom lib.viewbase import BaseFormView, BaseRedirectView\nfrom lib.email_messages import email_course_students\nfrom .forms import NewsForm\nfrom .models import News\n\n\nclass ListNewsView(CourseInstanceBaseView):\n    access_mode = ACCESS.TEACHER\n    template_name = \"news/list.html\"\n\n    def get_common_objects(self):\n        super().get_common_objects()\n        self.news = self.instance.news.all()\n        self.note(\"news\")\n\n\nclass EditNewsView(CourseInstanceMixin, BaseFormView):\n    access_mode = ACCESS.TEACHER\n    template_name = \"news/edit.html\"\n    form_class = NewsForm\n    news_item_kw = \"news_id\"\n\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n\n        news_id = self._get_kwarg(self.news_item_kw, default=None)\n        if news_id:\n            self.news_item = get_object_or_404(\n                News,\n                pk=news_id,\n                course_instance=self.instance\n            )\n            self.note(\"news_item\")\n        else:\n            self.news_item = News(course_instance=self.instance)\n\n        kwargs[\"instance\"] = self.news_item\n        return kwargs\n\n    def get_success_url(self):\n        return self.instance.get_url(\"news-list\")\n\n    def form_valid(self, form):\n        form.save()\n        if form.cleaned_data['email']:\n            subject = f\"[{settings.BRAND_NAME} course news] {self.instance.course.code}: {self.news_item.title}\"\n            if email_course_students(\n                self.instance,\n                subject,\n                self.news_item.body,\n                self.news_item.audience,\n            ) < 0:\n                messages.error(self.request, _('FAILED_TO_SEND_EMAIL'))\n        return super().form_valid(form)\n\n\nclass RemoveNewsView(CourseInstanceMixin, BaseRedirectView):\n    access_mode = ACCESS.TEACHER\n    news_item_kw = \"news_id\"\n\n    def get_resource_objects(self):\n        super().get_resource_objects()\n        self.news_item = get_object_or_404(\n            News,\n            id=self._get_kwarg(self.news_item_kw),\n            course_instance=self.instance,\n        )\n        self.note(\"news_item\")\n\n    def post(self, request, *args, **kwargs):\n        self.news_item.delete()\n        return self.redirect(self.instance.get_url(\"news-list\"))\n", "path": "news/views.py"}], "after_files": [{"content": "from django import forms\nfrom django.utils.translation import gettext_lazy as _\n\nfrom .models import News\n\n\nclass NewsForm(forms.ModelForm):\n\n    email_students = forms.BooleanField(\n        required=False,\n        label=_(\"SEND_EMAIL_TO_STUDENTS\"),\n    )\n    email_staff = forms.BooleanField(\n        required=False,\n        label=_(\"SEND_EMAIL_TO_STAFF\"),\n    )\n\n    class Meta:\n        model = News\n        fields = [\n            'audience',\n            'publish',\n            'pin',\n            'email_students',\n            'email_staff',\n            'title',\n            'body',\n        ]\n", "path": "news/forms.py"}, {"content": "import logging\nimport traceback\nfrom django.conf import settings\nfrom django.core.mail import send_mail, send_mass_mail\n\nfrom .helpers import Enum, build_aplus_url\nfrom course.models import CourseInstance\nfrom userprofile.models import UserProfile\n\n\nlogger = logging.getLogger('aplus.lib.email_messages')\n\n\ndef email_course_instance(instance, subject, message, everyone=False) -> bool:\n    \"\"\"\n    Sends an email to a course instance's technical support emails or teachers if technical support not set.\n    If everyone == True, sends emails to teachers anyway.\n    \"\"\"\n    recipients = []\n    if instance.technical_error_emails:\n        recipients = instance.technical_error_emails.split(\",\")\n    if everyone or not recipients:\n        recipients = instance.teachers.exclude(user__email='').values_list(\"user__email\", flat=True)\n\n    if not recipients:\n        raise ValueError(\"No recipients\")\n\n    try:\n        return send_mail(subject, message, settings.SERVER_EMAIL, recipients, True) == 1\n    except:\n        logger.exception('Failed to send course instance emails.')\n        raise\n\n\ndef email_course_error(request, exercise, message, exception=True):\n    \"\"\"\n    Sends error message to course instance's teachers or technical support emails if set.\n    \"\"\"\n    instance = exercise.course_instance\n\n    error_trace = \"-\"\n    if exception:\n        error_trace = traceback.format_exc()\n\n    if request:\n        request_fields = repr(request)\n    else:\n        request_fields = \"No request available\"\n\n    subject = settings.EXERCISE_ERROR_SUBJECT.format(\n        course=instance.course.code,\n        exercise=str(exercise))\n    body = settings.EXERCISE_ERROR_DESCRIPTION.format(\n        message=message,\n        exercise_url=build_aplus_url(\n            exercise.get_absolute_url(), user_url=True),\n        course_edit_url=build_aplus_url(\n            instance.get_url('course-details'), user_url=True),\n        error_trace=error_trace,\n        request_fields=request_fields)\n\n    try:\n        email_course_instance(instance, subject, body)\n    except: # pylint: disable=bare-except\n        pass\n\n\ndef email_course_students_and_staff(\n        instance: CourseInstance,\n        subject: str,\n        message: str,\n        student_audience: Enum = CourseInstance.ENROLLMENT_AUDIENCE.ALL_USERS,\n        include_staff: bool = False,\n        ) -> int:\n    \"\"\"\n    Sends an email to students and staff of the course. Audience parameter controls whether the mail goes\n    to all, just internal, or just external students.\n    Returns number of emails sent, or -1 in case of error.\n    \"\"\"\n    student_querys = {\n        CourseInstance.ENROLLMENT_AUDIENCE.ALL_USERS: instance.students,\n        CourseInstance.ENROLLMENT_AUDIENCE.INTERNAL_USERS: instance.students.filter(organization=settings.LOCAL_ORGANIZATION),\n        CourseInstance.ENROLLMENT_AUDIENCE.EXTERNAL_USERS: instance.students.exclude(organization=settings.LOCAL_ORGANIZATION),\n    }\n    recipients = student_querys.get(student_audience, UserProfile.objects.none()).exclude(user__email='').values_list(\"user__email\", flat=True)\n    if include_staff:\n        recipients = recipients.union(instance.course_staff.exclude(user__email='').values_list(\"user__email\", flat=True))\n\n    emails = tuple(map(lambda x: (subject, message, settings.SERVER_EMAIL, [x]), recipients))\n\n    try:\n        return send_mass_mail(emails)\n    except: # pylint: disable=bare-except\n        logger.exception('Failed to send course instance emails.')\n        return -1\n", "path": "lib/email_messages.py"}, {"content": "from django.conf import settings\nfrom django.contrib import messages\nfrom django.shortcuts import get_object_or_404\nfrom django.utils.translation import gettext_lazy as _\n\nfrom authorization.permissions import ACCESS\nfrom course.viewbase import CourseInstanceBaseView, CourseInstanceMixin\nfrom lib.viewbase import BaseFormView, BaseRedirectView\nfrom lib.email_messages import email_course_students_and_staff\nfrom .forms import NewsForm\nfrom .models import News\n\n\nclass ListNewsView(CourseInstanceBaseView):\n    access_mode = ACCESS.TEACHER\n    template_name = \"news/list.html\"\n\n    def get_common_objects(self):\n        super().get_common_objects()\n        self.news = self.instance.news.all()\n        self.note(\"news\")\n\n\nclass EditNewsView(CourseInstanceMixin, BaseFormView):\n    access_mode = ACCESS.TEACHER\n    template_name = \"news/edit.html\"\n    form_class = NewsForm\n    news_item_kw = \"news_id\"\n\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n\n        news_id = self._get_kwarg(self.news_item_kw, default=None)\n        if news_id:\n            self.news_item = get_object_or_404(\n                News,\n                pk=news_id,\n                course_instance=self.instance\n            )\n            self.note(\"news_item\")\n        else:\n            self.news_item = News(course_instance=self.instance)\n\n        kwargs[\"instance\"] = self.news_item\n        return kwargs\n\n    def get_success_url(self):\n        return self.instance.get_url(\"news-list\")\n\n    def form_valid(self, form):\n        form.save()\n        if form.cleaned_data['email_students'] or form.cleaned_data['email_staff']:\n            subject = f\"[{settings.BRAND_NAME} course news] {self.instance.course.code}: {self.news_item.title}\"\n            student_audience = self.news_item.audience if form.cleaned_data['email_students'] else None\n            if email_course_students_and_staff(\n                self.instance,\n                subject,\n                self.news_item.body,\n                student_audience,\n                form.cleaned_data['email_staff'],\n            ) < 0:\n                messages.error(self.request, _('FAILED_TO_SEND_EMAIL'))\n        return super().form_valid(form)\n\n\nclass RemoveNewsView(CourseInstanceMixin, BaseRedirectView):\n    access_mode = ACCESS.TEACHER\n    news_item_kw = \"news_id\"\n\n    def get_resource_objects(self):\n        super().get_resource_objects()\n        self.news_item = get_object_or_404(\n            News,\n            id=self._get_kwarg(self.news_item_kw),\n            course_instance=self.instance,\n        )\n        self.note(\"news_item\")\n\n    def post(self, request, *args, **kwargs):\n        self.news_item.delete()\n        return self.redirect(self.instance.get_url(\"news-list\"))\n", "path": "news/views.py"}]}