Skip to content

Commit

Permalink
Merge branch 'master' into maq/openedx#33589
Browse files Browse the repository at this point in the history
  • Loading branch information
abdullahQureshee authored Aug 22, 2024
2 parents e4d3eb3 + 44112aa commit ae3cc9e
Show file tree
Hide file tree
Showing 77 changed files with 3,026 additions and 1,983 deletions.
13 changes: 0 additions & 13 deletions cms/djangoapps/contentstore/config/waffle.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,3 @@
# .. toggle_warning: Flag course_experience.relative_dates should also be active for relative dates functionalities to work.
# .. toggle_tickets: https://openedx.atlassian.net/browse/AA-844
CUSTOM_RELATIVE_DATES = CourseWaffleFlag(f'{WAFFLE_NAMESPACE}.custom_relative_dates', __name__)


# .. toggle_name: studio.enable_course_update_notifications
# .. toggle_implementation: CourseWaffleFlag
# .. toggle_default: False
# .. toggle_description: Waffle flag to enable course update notifications.
# .. toggle_use_cases: temporary, open_edx
# .. toggle_creation_date: 14-Feb-2024
# .. toggle_target_removal_date: 14-Mar-2024
ENABLE_COURSE_UPDATE_NOTIFICATIONS = CourseWaffleFlag(
f'{WAFFLE_NAMESPACE}.enable_course_update_notifications',
__name__
)
8 changes: 3 additions & 5 deletions cms/djangoapps/contentstore/course_info_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from django.http import HttpResponseBadRequest
from django.utils.translation import gettext as _

from cms.djangoapps.contentstore.config.waffle import ENABLE_COURSE_UPDATE_NOTIFICATIONS
from cms.djangoapps.contentstore.utils import track_course_update_event, send_course_update_notification
from openedx.core.lib.xblock_utils import get_course_update_items
from xmodule.html_block import CourseInfoBlock # lint-amnesty, pylint: disable=wrong-import-order
Expand Down Expand Up @@ -93,10 +92,9 @@ def update_course_updates(location, update, passed_id=None, user=None, request_m
track_course_update_event(location.course_key, user, course_update_dict)

# send course update notification
if ENABLE_COURSE_UPDATE_NOTIFICATIONS.is_enabled(location.course_key):
send_course_update_notification(
location.course_key, course_update_dict["content"], user,
)
send_course_update_notification(
location.course_key, course_update_dict["content"], user,
)

# remove status key
if "status" in course_update_dict:
Expand Down
2 changes: 1 addition & 1 deletion cms/djangoapps/contentstore/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2255,7 +2255,7 @@ def send_course_update_notification(course_key, content, user):
"course_update_content": text_content if len(text_content.strip()) < 10 else "Click here to view",
**extra_context,
},
notification_type="course_update",
notification_type="course_updates",
content_url=f"{settings.LMS_ROOT_URL}/courses/{str(course_key)}/course/updates",
app_name="updates",
audience_filters={},
Expand Down
5 changes: 5 additions & 0 deletions cms/djangoapps/contentstore/views/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
from common.djangoapps.util.string_utils import _has_non_ascii_characters
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.credit.tasks import update_credit_course_requirements
from openedx.core.djangoapps.discussions.tasks import update_discussions_settings_from_course
from openedx.core.djangoapps.models.course_details import CourseDetails
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangolib.js_utils import dump_js_escaped_json
Expand Down Expand Up @@ -302,6 +303,10 @@ def course_handler(request, course_key_string=None):
else:
return HttpResponseBadRequest()
elif request.method == 'GET': # assume html
# Update course discussion settings, sometimes the course discussion settings are not updated
# when the course is created, so we need to update them here.
course_key = CourseKey.from_string(course_key_string)
update_discussions_settings_from_course(course_key)
if course_key_string is None:
return redirect(reverse('home'))
else:
Expand Down
4 changes: 2 additions & 2 deletions cms/djangoapps/contentstore/views/tests/test_course_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -717,8 +717,8 @@ def test_number_of_calls_to_db(self):
"""
Test to check number of queries made to mysql and mongo
"""
with self.assertNumQueries(29, table_ignorelist=WAFFLE_TABLES):
with check_mongo_calls(3):
with self.assertNumQueries(32, table_ignorelist=WAFFLE_TABLES):
with check_mongo_calls(5):
self.client.get_html(reverse_course_url('course_handler', self.course.id))


Expand Down
2 changes: 2 additions & 0 deletions cms/static/sass/studio-main-v1.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

// +Libs and Resets - *do not edit*
// ====================

@import '_builtin-block-variables';
@import 'bourbon/bourbon'; // lib - bourbon
@import 'vendor/bi-app/bi-app-ltr'; // set the layout for left to right languages
@import 'build-v1'; // shared app style assets/rendering
Binary file modified common/static/data/geoip/GeoLite2-Country.mmdb
Binary file not shown.
73 changes: 73 additions & 0 deletions common/static/sass/_builtin-block-variables.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* In pursuit of decoupling the built-in XBlocks from edx-platform's Sass build
* and ensuring comprehensive theming support in the extracted XBlocks,
* we need to expose Sass variables as CSS variables.
*
* Ticket/Issue: https://github.com/openedx/edx-platform/issues/35173
*/
@import 'bourbon/bourbon';
@import 'lms/theme/variables';
@import 'lms/theme/variables-v1';
@import 'cms/static/sass/partials/cms/theme/_variables';
@import 'cms/static/sass/partials/cms/theme/_variables-v1';
@import 'bootstrap/scss/variables';
@import 'vendor/bi-app/bi-app-ltr';
@import 'edx-pattern-library-shims/base/_variables.scss';

:root {
--action-primary-active-bg: $action-primary-active-bg;
--all-text-inputs: $all-text-inputs;
--base-font-size: $base-font-size;
--base-line-height: $base-line-height;
--baseline: $baseline;
--black: $black;
--black-t2: $black-t2;
--blue: $blue;
--blue-d1: $blue-d1;
--blue-d2: $blue-d2;
--blue-d4: $blue-d4;
--body-color: $body-color;
--border-color: $border-color;
--bp-screen-lg: $bp-screen-lg;
--btn-brand-focus-background: $btn-brand-focus-background;
--correct: $correct;
--danger: $danger;
--darkGrey: $darkGrey;
--error-color: $error-color;
--font-bold: $font-bold;
--font-family-sans-serif: $font-family-sans-serif;
--general-color-accent: $general-color-accent;
--gray: $gray;
--gray-300: $gray-300;
--gray-d1: $gray-d1;
--gray-l2: $gray-l2;
--gray-l3: $gray-l3;
--gray-l4: $gray-l4;
--gray-l6: $gray-l6;
--incorrect: $incorrect;
--lightGrey: $lightGrey;
--lighter-base-font-color: $lighter-base-font-color;
--link-color: $link-color;
--medium-font-size: $medium-font-size;
--partially-correct: $partially-correct;
--primary: $primary;
--shadow: $shadow;
--shadow-l1: $shadow-l1;
--sidebar-color: $sidebar-color;
--small-font-size: $small-font-size;
--static-path: $static-path;
--submitted: $submitted;
--success: $success;
--tmg-f2: $tmg-f2;
--tmg-s2: $tmg-s2;
--transparent: $transparent;
--uxpl-gray-background: $uxpl-gray-background;
--uxpl-gray-base: $uxpl-gray-base;
--uxpl-gray-dark: $uxpl-gray-dark;
--very-light-text: $very-light-text;
--warning: $warning;
--warning-color: $warning-color;
--warning-color-accent: $warning-color-accent;
--white: $white;
--yellow: $yellow;
}
4 changes: 4 additions & 0 deletions lms/djangoapps/bulk_email/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ class BulkEmailConfig(AppConfig):
Application Configuration for bulk_email.
"""
name = 'lms.djangoapps.bulk_email'

def ready(self):
import lms.djangoapps.bulk_email.signals # lint-amnesty, pylint: disable=unused-import
from edx_ace.signals import ACE_MESSAGE_SENT # lint-amnesty, pylint: disable=unused-import
30 changes: 28 additions & 2 deletions lms/djangoapps/bulk_email/signals.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
"""
Signal handlers for the bulk_email app
"""


from django.contrib.auth import get_user_model
from django.dispatch import receiver
from eventtracking import tracker

from common.djangoapps.student.models import CourseEnrollment
from openedx.core.djangoapps.user_api.accounts.signals import USER_RETIRE_MAILINGS
from edx_ace.signals import ACE_MESSAGE_SENT

from .models import Optout

Expand All @@ -24,3 +25,28 @@ def force_optout_all(sender, **kwargs): # lint-amnesty, pylint: disable=unused-

for enrollment in CourseEnrollment.objects.filter(user=user):
Optout.objects.get_or_create(user=user, course_id=enrollment.course.id)


@receiver(ACE_MESSAGE_SENT)
def ace_email_sent_handler(sender, **kwargs):
"""
When an email is sent using ACE, this method will create an event to detect ace email success status
"""
# Fetch the message object from kwargs, defaulting to None if not present
message = kwargs.get('message', None)

user_model = get_user_model()
try:
user_id = user_model.objects.get(email=message.recipient.email_address).id
except user_model.DoesNotExist:
user_id = None
course_email = message.context.get('course_email', None)
course_id = course_email.course_id if course_email else None
tracker.emit(
'edx.bulk_email.sent',
{
'message_type': message.name,
'course_id': course_id,
'user_id': user_id,
}
)
10 changes: 9 additions & 1 deletion lms/djangoapps/bulk_email/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from django.utils.translation import gettext as _
from django.utils.translation import override as override_language
from edx_django_utils.monitoring import set_code_owner_attribute
from eventtracking import tracker
from markupsafe import escape

from common.djangoapps.util.date_utils import get_default_time_display
Expand Down Expand Up @@ -467,7 +468,14 @@ def _send_course_email(entry_id, email_id, to_list, global_email_context, subtas
"send."
)
raise exc

tracker.emit(
'edx.bulk_email.created',
{
'course_id': str(course_email.course_id),
'to_list': to_list,
'total_recipients': total_recipients,
}
)
# Exclude optouts (if not a retry):
# Note that we don't have to do the optout logic at all if this is a retry,
# because we have presumably already performed the optout logic on the first
Expand Down
9 changes: 9 additions & 0 deletions lms/djangoapps/bulk_email/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
from django.http import Http404
from eventtracking import tracker
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey

Expand Down Expand Up @@ -60,4 +61,12 @@ def opt_out_email_updates(request, token, course_id):
course_id,
)

tracker.emit(
'edx.bulk_email.opt_out',
{
'course_id': course_id,
'user_id': user.id,
}
)

return render_to_response('bulk_email/unsubscribe_success.html', context)
2 changes: 2 additions & 0 deletions lms/djangoapps/discussion/django_comment_client/base/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1458,6 +1458,8 @@ def _test_unicode_data(self, text, mock_request):
@disable_signal(views, 'comment_created')
@disable_signal(views, 'comment_voted')
@disable_signal(views, 'comment_deleted')
@disable_signal(views, 'comment_flagged')
@disable_signal(views, 'thread_flagged')
class TeamsPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleStoreTestCase, MockRequestSetupMixin):
# Most of the test points use the same ddt data.
# args: user, commentable_id, status_code
Expand Down
20 changes: 17 additions & 3 deletions lms/djangoapps/discussion/rest_api/discussions_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,10 @@ def send_new_comment_notification(self):
self.parent_response and
self.creator.id != int(self.thread.user_id)
):
author_name = f"{self.parent_response.username}'s"
# use your if author of response is same as author of post.
# use 'their' if comment author is also response author.
author_name = (
author_pronoun = (
# Translators: Replier commented on "your" response to your post
_("your")
if self._response_and_thread_has_same_creator()
Expand All @@ -129,10 +130,12 @@ def send_new_comment_notification(self):
_("their")
if self._response_and_comment_has_same_creator()
else f"{self.parent_response.username}'s"

)
)
context = {
"author_name": str(author_name),
"author_pronoun": str(author_pronoun),
}
self._send_notification([self.thread.user_id], "new_comment", extra_context=context)

Expand Down Expand Up @@ -189,10 +192,21 @@ def send_response_on_followed_post_notification(self):
if not self.parent_id:
self._send_notification(users, "response_on_followed_post")
else:
author_name = f"{self.parent_response.username}'s"
# use 'their' if comment author is also response author.
author_pronoun = (
# Translators: Replier commented on "their" response in a post you're following
_("their")
if self._response_and_comment_has_same_creator()
else f"{self.parent_response.username}'s"
)
self._send_notification(
users,
"comment_on_followed_post",
extra_context={"author_name": self.parent_response.username}
extra_context={
"author_name": str(author_name),
"author_pronoun": str(author_pronoun),
}
)

def _create_cohort_course_audience(self):
Expand Down Expand Up @@ -300,7 +314,7 @@ def send_reported_content_notification(self):
content_type = thread_types[self.thread.type][getattr(self.thread, 'depth', 0)]

context = {
'username': self.creator.username,
'username': self.thread.username,
'content_type': content_type,
'content': thread_body
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def _assert_send_notification_called_with(self, mock_send_notification, expected

self.assertEqual(notification_type, "content_reported")
self.assertEqual(context, {
'username': 'test_user',
'username': self.thread.username,
'content_type': expected_content_type,
'content': 'Thread body'
})
Expand Down
10 changes: 7 additions & 3 deletions lms/djangoapps/discussion/rest_api/tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ def test_send_notification_to_parent_threads(self):
'replier_name': self.user_3.username,
'post_title': self.thread.title,
'author_name': 'dummy\'s',
'author_pronoun': 'dummy\'s',
'course_name': self.course.display_name,
'sender_id': self.user_3.id
}
Expand Down Expand Up @@ -399,7 +400,8 @@ def test_comment_creators_own_response(self):
expected_context = {
'replier_name': self.user_3.username,
'post_title': self.thread.title,
'author_name': 'your',
'author_name': 'dummy\'s',
'author_pronoun': 'your',
'course_name': self.course.display_name,
'sender_id': self.user_3.id,
}
Expand Down Expand Up @@ -441,7 +443,8 @@ def test_send_notification_to_followers(self, parent_id, notification_type):
'sender_id': self.user_2.id,
}
if parent_id:
expected_context['author_name'] = 'dummy'
expected_context['author_name'] = 'dummy\'s'
expected_context['author_pronoun'] = 'dummy\'s'
self.assertDictEqual(args.context, expected_context)
self.assertEqual(
args.content_url,
Expand Down Expand Up @@ -531,7 +534,8 @@ def test_new_comment_notification(self):
send_response_notifications(thread.id, str(self.course.id), self.user_2.id, parent_id=response.id)
handler.assert_called_once()
context = handler.call_args[1]['notification_data'].context
self.assertEqual(context['author_name'], 'their')
self.assertEqual(context['author_name'], 'dummy\'s')
self.assertEqual(context['author_pronoun'], 'their')


@override_waffle_flag(ENABLE_NOTIFICATIONS, active=True)
Expand Down
3 changes: 2 additions & 1 deletion lms/djangoapps/grades/grade_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from datetime import timedelta

from django.utils import timezone
from django.conf import settings

from openedx.core.djangoapps.content.course_overviews.models import CourseOverview

Expand All @@ -22,7 +23,7 @@ def are_grades_frozen(course_key):
if ENFORCE_FREEZE_GRADE_AFTER_COURSE_END.is_enabled(course_key):
course = CourseOverview.get_from_id(course_key)
if course.end:
freeze_grade_date = course.end + timedelta(30)
freeze_grade_date = course.end + timedelta(settings.GRADEBOOK_FREEZE_DAYS)
now = timezone.now()
return now > freeze_grade_date
return False
Loading

0 comments on commit ae3cc9e

Please sign in to comment.