edx-ace 1.11.3__py2.py3-none-any.whl → 1.12.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
edx_ace/__init__.py CHANGED
@@ -13,7 +13,7 @@ from .policy import Policy, PolicyResult
13
13
  from .recipient import Recipient
14
14
  from .recipient_resolver import RecipientResolver
15
15
 
16
- __version__ = '1.11.3'
16
+ __version__ = '1.12.0'
17
17
 
18
18
 
19
19
  __all__ = [
@@ -28,6 +28,7 @@ class ChannelType(Enum):
28
28
 
29
29
  EMAIL = 'email'
30
30
  PUSH = 'push'
31
+ BRAZE_PUSH = 'braze_push'
31
32
 
32
33
  def __str__(self):
33
34
  return str(self.value)
@@ -184,6 +185,8 @@ def get_channel_for_message(channel_type, message):
184
185
  channel_names = [settings.ACE_CHANNEL_DEFAULT_EMAIL]
185
186
  elif channel_type == ChannelType.PUSH and getattr(settings, "ACE_CHANNEL_DEFAULT_PUSH", None):
186
187
  channel_names = [settings.ACE_CHANNEL_DEFAULT_PUSH]
188
+ elif channel_type == ChannelType.BRAZE_PUSH and getattr(settings, "ACE_CHANNEL_BRAZE_PUSH", None):
189
+ channel_names = [settings.ACE_CHANNEL_BRAZE_PUSH]
187
190
 
188
191
  try:
189
192
  possible_channels = [
@@ -0,0 +1,64 @@
1
+ """
2
+ Channel for sending push notifications using braze.
3
+ """
4
+ import logging
5
+
6
+ from django.conf import settings
7
+
8
+ from edx_ace.channel import Channel, ChannelType
9
+ from edx_ace.message import Message
10
+ from edx_ace.renderers import RenderedPushNotification
11
+ from edx_ace.utils.braze import get_braze_client
12
+
13
+ LOG = logging.getLogger(__name__)
14
+
15
+
16
+ class BrazePushNotificationChannel(Channel):
17
+ """
18
+ A channel for sending push notifications using braze.
19
+ """
20
+ channel_type = ChannelType.BRAZE_PUSH
21
+ _CAMPAIGNS_SETTING = 'ACE_CHANNEL_BRAZE_PUSH_CAMPAIGNS'
22
+
23
+ @classmethod
24
+ def enabled(cls):
25
+ """
26
+ Returns: True iff braze client is available.
27
+ """
28
+ return bool(get_braze_client())
29
+
30
+ def deliver(self, message: Message, rendered_message: RenderedPushNotification) -> None:
31
+ """
32
+ Transmit a rendered message to a recipient.
33
+
34
+ Args:
35
+ message: The message to transmit.
36
+ rendered_message: The rendered content of the message that has been personalized
37
+ for this particular recipient.
38
+ """
39
+ braze_campaign = message.options['braze_campaign']
40
+ emails = message.options.get('emails') or [message.recipient.email_address]
41
+ campaign_id = self._campaign_id(braze_campaign)
42
+ if not campaign_id:
43
+ LOG.info('Could not find braze campaign for notification %s', braze_campaign)
44
+ return
45
+
46
+ try:
47
+ braze_client = get_braze_client()
48
+ braze_client.send_campaign_message(
49
+ campaign_id=campaign_id,
50
+ trigger_properties=message.context['post_data'],
51
+ emails=emails
52
+ )
53
+ LOG.info('Sent push notification for %s with Braze', braze_campaign)
54
+ except Exception as exc: # pylint: disable=broad-except
55
+ LOG.error(
56
+ 'Unable to send push notification for %s with Braze. Reason: %s',
57
+ braze_campaign,
58
+ str(exc)
59
+ )
60
+
61
+ @classmethod
62
+ def _campaign_id(cls, braze_campaign):
63
+ """Returns the campaign ID for a given ACE message name or None if no match is found"""
64
+ return getattr(settings, cls._CAMPAIGNS_SETTING, {}).get(braze_campaign)
edx_ace/presentation.py CHANGED
@@ -10,6 +10,7 @@ from edx_ace.channel import ChannelType
10
10
  RENDERERS = {
11
11
  ChannelType.EMAIL: renderers.EmailRenderer(),
12
12
  ChannelType.PUSH: renderers.PushNotificationRenderer(),
13
+ ChannelType.BRAZE_PUSH: renderers.BrazePushNotificationRenderer(),
13
14
  }
14
15
 
15
16
 
edx_ace/renderers.py CHANGED
@@ -99,3 +99,18 @@ class PushNotificationRenderer(AbstractRenderer):
99
99
  A renderer for :attr:`.ChannelType.PUSH` channels.
100
100
  """
101
101
  rendered_message_cls = RenderedPushNotification
102
+
103
+
104
+ @attr.s
105
+ class RenderedBrazePushNotification:
106
+ """
107
+ Encapsulates all values needed to send a :class:`.Message`
108
+ over an :attr:`.ChannelType.BRAZE_PUSH`.
109
+ """
110
+
111
+
112
+ class BrazePushNotificationRenderer(AbstractRenderer):
113
+ """
114
+ A renderer for :attr:`.ChannelType.PUSH` channels.
115
+ """
116
+ rendered_message_cls = RenderedBrazePushNotification
@@ -0,0 +1,105 @@
1
+ """
2
+ Tests for TestBrazePushNotificationChannel.
3
+ """
4
+ from unittest.mock import MagicMock, patch
5
+
6
+ import pytest
7
+
8
+ from django.contrib.auth import get_user_model
9
+ from django.test import TestCase, override_settings
10
+
11
+ from edx_ace.channel.braze_push_notification import BrazePushNotificationChannel
12
+ from edx_ace.message import Message
13
+ from edx_ace.recipient import Recipient
14
+ from edx_ace.renderers import RenderedBrazePushNotification
15
+
16
+ BRAZE_URL = "https://example.braze.com"
17
+ API_KEY = "test-api-key"
18
+ User = get_user_model()
19
+
20
+
21
+ @pytest.mark.django_db
22
+ @override_settings(
23
+ EDX_BRAZE_API_KEY=API_KEY,
24
+ EDX_BRAZE_API_SERVER=BRAZE_URL,
25
+ )
26
+ class TestBrazePushNotificationChannel(TestCase):
27
+
28
+ def setUp(self):
29
+ super().setUp()
30
+ self.user = User.objects.create(username='username', email='email@example.com')
31
+ self.lms_user_id = self.user.id
32
+ self.mocked_post_data = {
33
+ 'notification_type': 'new_response',
34
+ 'course_id': 'course-v1:edX+DemoX+Demo_Course',
35
+ 'content_url': 'http://localhost',
36
+ 'replier_name': 'verified',
37
+ 'post_title': 'New test response',
38
+ 'course_name': 'Demonstration Course',
39
+ 'thread_id': '67bedeb9ceb0b101343294c5',
40
+ 'topic_id': 'i4x-edx-eiorguegnru-course-foobarbaz',
41
+ 'response_id': '67ffa1f1ceb0b10134db3d8e',
42
+ 'comment_id': None,
43
+ 'strong': 'strong', 'p': 'p'
44
+ }
45
+
46
+ self.mocked_payload = {
47
+ 'campaign_id': '1234test',
48
+ 'trigger_properties': self.mocked_post_data,
49
+ 'emails': ['edx@example.com']
50
+ }
51
+
52
+ @patch('edx_ace.channel.braze_push_notification.get_braze_client', return_value=True)
53
+ def test_enabled(self, mock_braze_client):
54
+ """
55
+ Test that the channel is enabled when the settings are configured.
56
+ """
57
+ assert BrazePushNotificationChannel.enabled()
58
+
59
+ @patch('edx_ace.channel.braze_push_notification.get_braze_client', return_value=False)
60
+ def test_disabled(self, mock_braze_client):
61
+ """
62
+ Test that the channel is disabled when the settings are not configured.
63
+ """
64
+ assert not BrazePushNotificationChannel.enabled()
65
+
66
+ @override_settings(ACE_CHANNEL_BRAZE_PUSH_CAMPAIGNS={'new_response': "1234test"})
67
+ @patch('edx_ace.channel.braze_push_notification.get_braze_client')
68
+ def test_deliver_success(self, mock_braze_function):
69
+ mock_braze_client = MagicMock()
70
+ mock_braze_function.return_value = mock_braze_client
71
+ mock_braze_client.send_campaign_message = MagicMock(return_value=True)
72
+
73
+ rendered_message = RenderedBrazePushNotification()
74
+ message = Message(
75
+ app_label='testapp',
76
+ name="test_braze",
77
+ recipient=Recipient(lms_user_id="1", email_address="user@example.com"),
78
+ context={'post_data': self.mocked_post_data},
79
+ options={'emails': ['edx@example.com'], 'braze_campaign': 'new_response'}
80
+ )
81
+ channel = BrazePushNotificationChannel()
82
+ channel.deliver(message, rendered_message)
83
+ mock_braze_client.send_campaign_message.assert_called_once()
84
+ args, kwargs = mock_braze_client.send_campaign_message.call_args
85
+
86
+ # Verify the payload
87
+ self.assertEqual(kwargs, self.mocked_payload)
88
+
89
+ @patch('edx_ace.channel.braze_push_notification.get_braze_client')
90
+ def test_campaign_not_configured(self, mock_braze_function):
91
+ mock_braze_client = MagicMock()
92
+ mock_braze_function.return_value = mock_braze_client
93
+ mock_braze_client.send_campaign_message = MagicMock(return_value=True)
94
+
95
+ rendered_message = RenderedBrazePushNotification()
96
+ message = Message(
97
+ app_label='testapp',
98
+ name="test_braze",
99
+ recipient=Recipient(lms_user_id="1", email_address="user@example.com"),
100
+ context={'post_data': self.mocked_post_data},
101
+ options={'emails': ['edx@example.com'], 'braze_campaign': 'new_response'}
102
+ )
103
+ channel = BrazePushNotificationChannel()
104
+ channel.deliver(message, rendered_message)
105
+ mock_braze_client.send_campaign_message.assert_not_called()
@@ -24,12 +24,16 @@ class TestPolicy(TestCase):
24
24
  PolicyCase(deny_values=[set(ChannelType)], expected_channels=set()),
25
25
 
26
26
  # deny only email
27
- PolicyCase(deny_values=[{ChannelType.EMAIL}], expected_channels={ChannelType.PUSH}), # single policy
28
- PolicyCase(deny_values=[{ChannelType.EMAIL}, set()], expected_channels={ChannelType.PUSH}), # multiple policies
29
-
30
- # deny both email and push
31
- PolicyCase(deny_values=[{ChannelType.EMAIL, ChannelType.PUSH}], expected_channels=set()), # single policy
32
- PolicyCase(deny_values=[{ChannelType.EMAIL}, {ChannelType.PUSH}], expected_channels=set()), # multiple policies
27
+ PolicyCase(deny_values=[{ChannelType.EMAIL}],
28
+ expected_channels={ChannelType.PUSH, ChannelType.BRAZE_PUSH}), # single policy
29
+ PolicyCase(deny_values=[{ChannelType.EMAIL}, set()],
30
+ expected_channels={ChannelType.PUSH, ChannelType.BRAZE_PUSH}), # multiple policies
31
+
32
+ # deny email, push and braze_push
33
+ PolicyCase(deny_values=[{ChannelType.EMAIL, ChannelType.PUSH, ChannelType.BRAZE_PUSH}],
34
+ expected_channels=set()), # single policy
35
+ PolicyCase(deny_values=[{ChannelType.EMAIL}, {ChannelType.PUSH},
36
+ {ChannelType.BRAZE_PUSH}], expected_channels=set()), # multiple policies
33
37
 
34
38
  # deny all and email
35
39
  PolicyCase(deny_values=[{ChannelType.EMAIL}, set(ChannelType)], expected_channels=set()),
@@ -0,0 +1,59 @@
1
+ """
2
+ Test cases for utils.braze
3
+ """
4
+ from unittest.mock import patch
5
+
6
+ import pytest
7
+
8
+ from django.contrib.auth import get_user_model
9
+ from django.test import TestCase, override_settings
10
+
11
+ from edx_ace.utils.braze import get_braze_client
12
+
13
+ BRAZE_URL = "https://example.braze.com"
14
+ API_KEY = "test-api-key"
15
+ User = get_user_model()
16
+
17
+
18
+ @pytest.mark.django_db
19
+ class TestBrazeClient(TestCase):
20
+ """ Test cases for utils.braze """
21
+
22
+ @patch('edx_ace.utils.braze.BrazeClient')
23
+ def test_disabled(self, mock_braze_client):
24
+ """
25
+ Test that the channel is settings aren't configured.
26
+ """
27
+ result = get_braze_client()
28
+ self.assertEqual(result, None)
29
+ mock_braze_client.assert_not_called()
30
+
31
+ @override_settings(ACE_CHANNEL_BRAZE_API_KEY=API_KEY)
32
+ @patch('edx_ace.utils.braze.BrazeClient')
33
+ def test_braze_url_not_configured(self, mock_braze_client):
34
+ """
35
+ Test that the channel is settings aren't configured.
36
+ """
37
+ result = get_braze_client()
38
+ self.assertEqual(result, None)
39
+ mock_braze_client.assert_not_called()
40
+
41
+ @override_settings(ACE_CHANNEL_BRAZE_REST_ENDPOINT=API_KEY)
42
+ @patch('edx_ace.utils.braze.BrazeClient')
43
+ def test_braze_api_key_not_configured(self, mock_braze_client):
44
+ """
45
+ Test that the channel is settings aren't configured.
46
+ """
47
+ result = get_braze_client()
48
+ self.assertEqual(result, None)
49
+ mock_braze_client.assert_not_called()
50
+
51
+ @override_settings(ACE_CHANNEL_BRAZE_REST_ENDPOINT=API_KEY, ACE_CHANNEL_BRAZE_API_KEY=API_KEY)
52
+ @patch('edx_ace.utils.braze.BrazeClient', return_value=True)
53
+ def test_success(self, mock_braze_client):
54
+ """
55
+ Test that the channel is settings aren't configured.
56
+ """
57
+ result = get_braze_client()
58
+ self.assertEqual(result, True)
59
+ mock_braze_client.assert_called_once()
edx_ace/utils/braze.py ADDED
@@ -0,0 +1,27 @@
1
+ """
2
+ Helper Methods related to braze client
3
+ """
4
+
5
+ try:
6
+ from braze.client import BrazeClient
7
+ except ImportError:
8
+ BrazeClient = None
9
+ from django.conf import settings
10
+
11
+
12
+ def get_braze_client():
13
+ """ Returns a Braze client. """
14
+ if not BrazeClient:
15
+ return None
16
+
17
+ braze_api_key = getattr(settings, 'ACE_CHANNEL_BRAZE_API_KEY', None)
18
+ braze_api_url = getattr(settings, 'ACE_CHANNEL_BRAZE_REST_ENDPOINT', None)
19
+
20
+ if not braze_api_key or not braze_api_url:
21
+ return None
22
+
23
+ return BrazeClient(
24
+ api_key=braze_api_key,
25
+ api_url=f"https://{braze_api_url}",
26
+ app_id='',
27
+ )
edx_ace/utils/signals.py CHANGED
@@ -1,6 +1,8 @@
1
1
  """
2
2
  Utils for signals.
3
3
  """
4
+ from django.utils import translation
5
+
4
6
  from edx_ace.signals import ACE_MESSAGE_SENT
5
7
 
6
8
 
@@ -42,5 +44,7 @@ def send_ace_message_sent_signal(channel, message):
42
44
  'options': message.options,
43
45
  'uuid': str(message.uuid),
44
46
  'send_uuid': str(message.send_uuid),
47
+ 'message_language': message.language,
48
+ 'translation_language': translation.get_language()
45
49
  }
46
50
  ACE_MESSAGE_SENT.send(sender=channel, message=make_serializable_object(data))
@@ -1,13 +1,12 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: edx-ace
3
- Version: 1.11.3
3
+ Version: 1.12.0
4
4
  Summary: Framework for Messaging
5
5
  Home-page: https://github.com/openedx/edx-ace
6
6
  Author: edX
7
7
  Author-email: oscm@edx.org
8
8
  License: AGPL 3.0
9
9
  Keywords: Django edx
10
- Platform: UNKNOWN
11
10
  Classifier: Development Status :: 3 - Alpha
12
11
  Classifier: Framework :: Django
13
12
  Classifier: Framework :: Django :: 4.2
@@ -18,9 +17,11 @@ Classifier: Programming Language :: Python :: 3
18
17
  Classifier: Programming Language :: Python :: 3.8
19
18
  Classifier: Programming Language :: Python :: 3.12
20
19
  Description-Content-Type: text/x-rst
20
+ License-File: LICENSE.txt
21
21
  Requires-Dist: Django>=2.2
22
22
  Requires-Dist: attrs>=17.2.0
23
23
  Requires-Dist: django-push-notifications
24
+ Requires-Dist: edx-braze-client==1.0.1
24
25
  Requires-Dist: edx-django-utils>=5.14.2
25
26
  Requires-Dist: firebase-admin
26
27
  Requires-Dist: python-dateutil
@@ -39,7 +40,7 @@ edX Automated Communication Engine (A.C.E.)
39
40
  |doc-badge| |pyversions-badge|
40
41
 
41
42
  Purpose
42
- =======
43
+ *******
43
44
 
44
45
  The `automated communication engine <https://edx-ace.readthedocs.io/en/latest/>`_, A.C.E. for short, is a Django app
45
46
  for messaging learners on the Open edX platform. This
@@ -49,12 +50,12 @@ are the currently supported delivery channels. In the future we may add support
49
50
  notifications.
50
51
 
51
52
  Getting Started
52
- ===============
53
+ ***************
53
54
 
54
- For instructions on starting local development, see `Getting Started <https://github.com/openedx/edx-ace/blob/master/docs/getting_started.rst>`_.
55
+ For instructions on starting local development, see `Getting Started page <https://github.com/openedx/edx-ace/blob/master/docs/getting_started.rst>`_.
55
56
 
56
57
  Getting Help
57
- ============
58
+ ************
58
59
 
59
60
  Documentation
60
61
  -------------
@@ -79,14 +80,14 @@ can provide.
79
80
 
80
81
  https://github.com/openedx/edx-ace/issues
81
82
 
82
- For more information about these options, see the `Getting Help`_ page.
83
+ For more information about these options, see the `help`_ page.
83
84
 
84
85
  .. _Slack invitation: https://openedx.org/slack
85
86
  .. _community Slack workspace: https://openedx.slack.com/
86
- .. _Getting Help: https://openedx.org/getting-help
87
+ .. _help: https://openedx.org/getting-help
87
88
 
88
89
  License
89
- =======
90
+ *******
90
91
 
91
92
  The code in this repository is licensed under the AGPL 3.0 unless
92
93
  otherwise noted.
@@ -94,7 +95,7 @@ otherwise noted.
94
95
  Please see ``LICENSE.txt`` for details.
95
96
 
96
97
  Contributing
97
- ============
98
+ ************
98
99
 
99
100
  Contributions are very welcome.
100
101
 
@@ -111,14 +112,14 @@ Issue report template should be automatically applied if you are sending it from
111
112
  can find it at `ISSUE_TEMPLATE.md <https://github.com/openedx/edx-ace/blob/master/.github/ISSUE_TEMPLATE.md>`_
112
113
 
113
114
  The Open edX Code of Conduct
114
- ============================
115
+ ****************************
115
116
 
116
117
  All community members are expected to follow the `Open edX Code of Conduct`_.
117
118
 
118
119
  .. _Open edX Code of Conduct: https://openedx.org/code-of-conduct/
119
120
 
120
121
  People
121
- ======
122
+ ******
122
123
 
123
124
  The assigned maintainers for this component and other project details may be
124
125
  found in `Backstage`_. Backstage pulls this data from the ``catalog-info.yaml``
@@ -127,7 +128,7 @@ file in this repo.
127
128
  .. _Backstage: https://open-edx-backstage.herokuapp.com/catalog/default/component/edx-ace
128
129
 
129
130
  Reporting Security Issues
130
- =========================
131
+ *************************
131
132
 
132
133
  Please do not report security issues in public. Please email security@openedx.org.
133
134
 
@@ -168,7 +169,7 @@ Please do not report security issues in public. Please email security@openedx.or
168
169
 
169
170
 
170
171
  Change Log
171
- ----------
172
+ ##########
172
173
 
173
174
  ..
174
175
  All enhancements and patches to edx_ace will be documented
@@ -181,54 +182,55 @@ Change Log
181
182
  .. There should always be an "Unreleased" section for changes pending release.
182
183
 
183
184
  Unreleased
184
- ~~~~~~~~~~
185
+ **********
185
186
 
186
187
  [1.8.0] - 2024-03-06
187
- ~~~~~~~~~~~~~~~~~~~~
188
+ --------------------
189
+
188
190
  * Added support for ``Python 3.12``
189
191
  * Dropped support for ``Django 3.2``
190
192
 
191
193
 
192
194
  [1.7.0] - 2023-07-21
193
- ~~~~~~~~~~~~~~~~~~~~
195
+ --------------------
194
196
 
195
197
  * Switch from ``edx-sphinx-theme`` to ``sphinx-book-theme`` since the former is
196
198
  deprecated
197
199
  * Added support for django 4.2
198
200
 
199
201
  [1.6.1] - 2023-02-07
200
- ~~~~~~~~~~~~~~~~~~~~
202
+ --------------------
201
203
 
202
204
  * Updated code coverage target in https://github.com/openedx/edx-ace/pull/189
203
205
  * Updated Python Requirements in https://github.com/openedx/edx-ace/pull/199
204
206
  * **Full Changelog**: https://github.com/openedx/edx-ace/compare/v1.6.0...v1.6.1
205
207
 
206
208
  [1.5.0] - 2022-02-15
207
- ~~~~~~~~~~~~~~~~~~~~
209
+ --------------------
208
210
 
209
211
  * Added support for Django40
210
212
  * Removed support for Django22, 30 and 31
211
213
 
212
214
  [1.4.1] - 2021-12-06
213
- ~~~~~~~~~~~~~~~~~~~~
215
+ --------------------
214
216
 
215
217
  * Adds in the ability to override frequency caps for Braze emails. Can be accessed via
216
218
  Message options using the key ``override_frequency_capping``. All emails containing the
217
219
  ``transactional`` Message option will also override frequency caps.
218
220
 
219
221
  [1.4.0] - 2021-11-08
220
- ~~~~~~~~~~~~~~~~~~~~
222
+ --------------------
221
223
 
222
224
  * Deprecate the action_links property
223
225
  * Add a get_action_links method and template tag to allow passing arguments to action links
224
226
 
225
227
  [1.3.1] - 2021-08-17
226
- ~~~~~~~~~~~~~~~~~~~~
228
+ --------------------
227
229
 
228
230
  * Adjust name ``handles_delivery_for_message`` to ``overrides_delivery_for_message``
229
231
 
230
232
  [1.3.0] - 2021-08-16
231
- ~~~~~~~~~~~~~~~~~~~~
233
+ --------------------
232
234
 
233
235
  * New channel method ``handles_delivery_for_message`` for allowing a default channel
234
236
  to claim a message, even if it would normally be delivered to the configured
@@ -238,29 +240,29 @@ Unreleased
238
240
  needed.
239
241
 
240
242
  [1.2.0] - 2021-07-16
241
- ~~~~~~~~~~~~~~~~~~~~
243
+ --------------------
242
244
 
243
245
  * Added support for django 3.2
244
246
 
245
247
  [1.1.1] - 2021-07-09
246
- ~~~~~~~~~~~~~~~~~~~~
248
+ --------------------
247
249
 
248
250
  * Removed upper constraint from Django
249
251
 
250
252
  [1.1.0] - 2021-03-26
251
- ~~~~~~~~~~~~~~~~~~~~
253
+ --------------------
252
254
 
253
255
  * Braze: Add ACE_CHANNEL_BRAZE_FROM_EMAIL setting to override the normal from address
254
256
  * Sailthru: Remove Braze rollout waffle flag
255
257
 
256
258
  [1.0.1] - 2021-03-15
257
- ~~~~~~~~~~~~~~~~~~~~
259
+ --------------------
258
260
 
259
261
  * Braze: Add an unsubscribe action link
260
262
  * Braze: Don't ask Braze to inline css, as ACE templates already have inline css
261
263
 
262
264
  [1.0.0] - 2021-03-11
263
- ~~~~~~~~~~~~~~~~~~~~
265
+ --------------------
264
266
 
265
267
  * BREAKING: Recipient objects now take `lms_user_id` instead of `username`
266
268
  * New `braze_email` backend, needing the following new configuration:
@@ -271,59 +273,52 @@ Unreleased
271
273
  * ACE_CHANNEL_BRAZE_CAMPAIGNS (an optional dictionary of ACE message names to Braze campaign identifiers)
272
274
 
273
275
  [0.1.18] - 2020-11-19
274
- ~~~~~~~~~~~~~~~~~~~~~
276
+ ---------------------
275
277
 
276
278
  * Updated he travis-badge in README.rst to point to travis-ci.com
277
279
 
278
280
  [0.1.17] - 2020-10-19
279
- ~~~~~~~~~~~~~~~~~~~~~
281
+ ---------------------
280
282
 
281
283
  * Use IntEnum to avoid silent failure in value comparisons
282
284
 
283
285
  [0.1.16] - 2020-10-17
284
- ~~~~~~~~~~~~~~~~~~~~~
286
+ ---------------------
285
287
 
286
288
  * Fixed Enum usage for Python 3.8 to avoid TypeError when comparing values
287
289
 
288
290
  [0.1.15] - 2020-03-11
289
- ~~~~~~~~~~~~~~~~~~~~~
291
+ ---------------------
290
292
 
291
293
  * Added support for Python 3.8
292
294
  * Removed support for Django 2.0 and 2.1
293
295
 
294
296
  [0.1.14] - 2020-03-11
295
- ~~~~~~~~~~~~~~~~~~~~~
297
+ ---------------------
296
298
 
297
299
  * Fix trivial warning from deprecated use of attr library.
298
300
 
299
301
  [0.1.13] - 2019-12-06
300
- ~~~~~~~~~~~~~~~~~~~~~
302
+ ---------------------
301
303
 
302
304
  * Django22 Support.
303
305
 
304
306
  [0.1.12] - 2019-10-16
305
- ~~~~~~~~~~~~~~~~~~~~~
307
+ ---------------------
306
308
 
307
309
  * Reply_to field added in emails.
308
310
 
309
311
  [0.1.10] - 2018-11-01
310
- ~~~~~~~~~~~~~~~~~~~~~
312
+ ---------------------
311
313
 
312
314
  * Django lazy text translations are handled properly.
313
315
 
314
-
315
316
  [0.1.9] - 2018-07-13
316
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
317
+ --------------------
317
318
 
318
319
  * Updated delivery logging
319
320
 
320
-
321
321
  [0.1.0] - 2017-08-08
322
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
323
-
324
- Added
325
- _____
326
-
327
- * First release on PyPI.
328
-
322
+ --------------------
329
323
 
324
+ * [Added] First release on PyPI.
@@ -1,4 +1,4 @@
1
- edx_ace/__init__.py,sha256=Ro4txh3_BvOxSMMgQlibuMMDQCU7M943OCbj2MTUGb8,636
1
+ edx_ace/__init__.py,sha256=cJnxgaJn9f2ItO7dGSOm44vUsQw3O7-KC1oL-RghtSc,636
2
2
  edx_ace/ace.py,sha256=0nP8zvIWiME4qmpMT1PaV4nRDG6YR9Dxm7A6FGoBa8I,2501
3
3
  edx_ace/apps.py,sha256=xNedkdW6TNpm-W1uxnj3Vre2R1akkh2n_7DkSfKXmAk,216
4
4
  edx_ace/delivery.py,sha256=zo5lNHngycEn0tu-e3JM26njRVmaAttAH3D5ohz_468,2812
@@ -6,14 +6,15 @@ edx_ace/errors.py,sha256=y0MqT55qXLkpn_r5LVhHcYuU2-zHs56CQTAbNCqb72k,1065
6
6
  edx_ace/message.py,sha256=lhmPor9vnaLvC4NPyRgB-obpGjGv9Lni0obcOUTgyCg,8340
7
7
  edx_ace/monitoring.py,sha256=6nEcAJMCr9keCTJw9JjGecTg_J1ubcGJfuGiFq2B8G4,257
8
8
  edx_ace/policy.py,sha256=Zi0oupnmvgnW7jOkEpgsOd-gj6isR-3Mna2SxsdG4U8,2915
9
- edx_ace/presentation.py,sha256=XCQwPsl_2LZMcyF_aFX7b91d7rEzmQJiv8XgOSCyLQ0,838
9
+ edx_ace/presentation.py,sha256=o_BbUOzNxBqmcGHIq0PAhxouDo3cVaEnn7uJGCK9y4o,909
10
10
  edx_ace/recipient.py,sha256=ogZjjKorAc6yiqvcnXcAMsHz2uAfhYU5qLPUoifofXQ,576
11
11
  edx_ace/recipient_resolver.py,sha256=ChY0cgLSt_HioKSHyuCh7iHSJOsBWuivsOvAc6QyedE,959
12
- edx_ace/renderers.py,sha256=eJcTWyRhny-3PLHDV4Rozk1TJ0fjsqgOcFOWO_TTngk,3143
12
+ edx_ace/renderers.py,sha256=f1bPdmWpBvMa9OCPsq0NgnxsZi2Y4gwW5ZcYk9Qmpsw,3499
13
13
  edx_ace/serialization.py,sha256=EptnQqbI9j5kVqdUDAlm2pcm3dUsFwsP9tAji3y5uqc,3775
14
14
  edx_ace/signals.py,sha256=7kwpw7SgpPP6TRTtTQeGEOZ2cD_AcD4sSrpVxd_a19w,160
15
- edx_ace/channel/__init__.py,sha256=bQ8rpb_o0CvyWxZjOkmU9Au2S4kUCeKM8upvUb3yG7g,7030
15
+ edx_ace/channel/__init__.py,sha256=PzfSJhBbsW5a8r1NnMm5q1PhwSadSoeIehjhU-C88pM,7221
16
16
  edx_ace/channel/braze.py,sha256=k-R9jZpiBQTV0FQ3Z2gOipVgxNpBMPSLBL71muh9go4,10240
17
+ edx_ace/channel/braze_push_notification.py,sha256=XzFARmDotqthpARqvdvmcUy-ieEzusY-pH449nuciM4,2239
17
18
  edx_ace/channel/django_email.py,sha256=9TNdiFJ2U3QjKEriUl3_9twAFkH-E2EmrWujStUOWN0,2174
18
19
  edx_ace/channel/file.py,sha256=_Jx8o5Cw8IpEdaF7g-ljF7CEqswBHmuwyupm_tkI2wk,3028
19
20
  edx_ace/channel/mixins.py,sha256=kmSBFcBU2OhMqlzaDZPrZpT3lh6ccte0cQeCCvo8FNc,1313
@@ -26,9 +27,10 @@ edx_ace/tests/test_ace.py,sha256=H5ss4ah-v5XDJ47udl6Ob6iSVdcX0qkjPI2h0kgWp1k,327
26
27
  edx_ace/tests/test_date.py,sha256=RteoiCrc269BXnIerrtz8I0K1weldZ_Qg9Oc0Nd8ixs,639
27
28
  edx_ace/tests/test_delivery.py,sha256=6nTSm3y28U0J0ChfduVPxkRyMA-IVEZ9H8TkmSHktAI,5787
28
29
  edx_ace/tests/test_message.py,sha256=uAf3XltW605WieNGdi0u5p6bY_oSqLsmV5pHU3J_TiQ,4430
29
- edx_ace/tests/test_policy.py,sha256=T6Mm_q1Bi935WMBPS7sK5XbXM5izQ3y0KW0yiaQ5_GU,1597
30
+ edx_ace/tests/test_policy.py,sha256=bksNJntzD7YXaVx95LCR4n9uyxAfugXlen3HElUHsaU,1791
30
31
  edx_ace/tests/test_presentation.py,sha256=elgVz_sR04h3KKs73G0qQBzt0CazOPWKrDF2rVC_R4U,541
31
32
  edx_ace/tests/channel/test_braze.py,sha256=J7p9_xF5NTzZDC-9P8aFsV3cLc2RyDMBNVlyimwORuU,7296
33
+ edx_ace/tests/channel/test_braze_push_notification.py,sha256=1BhDIOjuCnZCjKWTy3jpf7978SyeLSv0xJqZ1Av-DXU,4241
32
34
  edx_ace/tests/channel/test_channel_helpers.py,sha256=QwzHhdpEGkdW_WV0bfLkj8nBzHy4zFoo_lsIVlN31QU,3196
33
35
  edx_ace/tests/channel/test_django_email.py,sha256=FCllUVAAtGEtDsosU-auf7zOApk-12oM_xWYs7CqVn8,4114
34
36
  edx_ace/tests/channel/test_file_email.py,sha256=0kWb5xhdHJMgMNrnlEM2QV4NFLZERWgRU-3Lx0MQxLM,1795
@@ -36,15 +38,17 @@ edx_ace/tests/channel/test_push_notification.py,sha256=u6BBplfPwQLMRL02VFlWlNDEw
36
38
  edx_ace/tests/channel/test_sailthru.py,sha256=Hfs-v2y9N9H2u0LlA_cJkhcuU0iC02wzwTt4pHsRS7o,3197
37
39
  edx_ace/tests/test_templates/testapp/edx_ace/testmessage/email/body.html,sha256=gDeXQeQFnC2EFzdJ-OwWL7yDdkiR42sUYIxyeJJUD9s,646
38
40
  edx_ace/tests/test_templates/testapp/edx_ace/testmessage/email/head.html,sha256=XvxX2Go_eKUtG0Oo1qPPMOtdS2IoGi7PSY98Di_Ev3s,19
41
+ edx_ace/tests/utils/test_braze_utils.py,sha256=aywPWmpnhUqSToWPiKMpduAyVojZ_s1ZeKzR3oSLu-o,1899
39
42
  edx_ace/tests/utils/test_signals.py,sha256=k0F76oxcnECd95MeGw666YD3d2z-mIx4AS_MTYjwY5Q,1513
40
43
  edx_ace/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
+ edx_ace/utils/braze.py,sha256=I0CcJELYkbfMTI3EWGkb8-unxO3Wkxu6C1GHA93H_0Y,622
41
45
  edx_ace/utils/date.py,sha256=Rmz3RAUCdd30hu1qcKH7FmAypaw1aSoZg-yfZig1c8A,1483
42
46
  edx_ace/utils/once.py,sha256=sY3szBh3gvvAjrKbKq4S2mCejompjh5YcYD7XOhzjGU,2024
43
47
  edx_ace/utils/plugins.py,sha256=U-l-eU2uWUiiwYV-H-2DfmwjoksqskAsYwS7QnThy2Q,2090
44
- edx_ace/utils/signals.py,sha256=zvHE0qW-oQUKNhj2bwMu16-XEBmJyjraIgcr2w0oHRw,1494
45
- edx_ace-1.11.3.dist-info/LICENSE.txt,sha256=VrSJ4gO4NCpskzfNHbaTB4VcN9Q213YdcHbpOZSwcOA,35138
46
- edx_ace-1.11.3.dist-info/METADATA,sha256=e7DAzLVDD37_wilR5pbrElV3ovhbzLzFlq1AU_lGzRY,10106
47
- edx_ace-1.11.3.dist-info/WHEEL,sha256=fS9sRbCBHs7VFcwJLnLXN1MZRR0_TVTxvXKzOnaSFs8,110
48
- edx_ace-1.11.3.dist-info/entry_points.txt,sha256=fiR8u0PqGyp2qIiJxcSrYhIZ3gEwl0vIMnTPWegXwRI,332
49
- edx_ace-1.11.3.dist-info/top_level.txt,sha256=5eg_80KI88VkeiCVqZUqcYcc_PfPOg8o1GA4HxsiRU8,8
50
- edx_ace-1.11.3.dist-info/RECORD,,
48
+ edx_ace/utils/signals.py,sha256=efmnxI78juMLo6PJkPyfJ6pYTMY3kHnGy-_IR8FsDm8,1637
49
+ edx_ace-1.12.0.dist-info/LICENSE.txt,sha256=VrSJ4gO4NCpskzfNHbaTB4VcN9Q213YdcHbpOZSwcOA,35138
50
+ edx_ace-1.12.0.dist-info/METADATA,sha256=QmLbJDtWiKXzqEtSTymkv6skzRvAZ1zqAWD91EMKLGc,10078
51
+ edx_ace-1.12.0.dist-info/WHEEL,sha256=Kh9pAotZVRFj97E15yTA4iADqXdQfIVTHcNaZTjxeGM,110
52
+ edx_ace-1.12.0.dist-info/entry_points.txt,sha256=9jbgYKntqyu4kP1pI_Js1BSWqKBG7OacftVn1BAKsLI,413
53
+ edx_ace-1.12.0.dist-info/top_level.txt,sha256=5eg_80KI88VkeiCVqZUqcYcc_PfPOg8o1GA4HxsiRU8,8
54
+ edx_ace-1.12.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.44.0)
2
+ Generator: bdist_wheel (0.45.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any
@@ -1,7 +1,7 @@
1
1
  [openedx.ace.channel]
2
2
  braze_email = edx_ace.channel.braze:BrazeEmailChannel
3
+ braze_push = edx_ace.channel.braze_push_notification:BrazePushNotificationChannel
3
4
  django_email = edx_ace.channel.django_email:DjangoEmailChannel
4
5
  file_email = edx_ace.channel.file:FileEmailChannel
5
6
  push_notification = edx_ace.channel.push_notification:PushNotificationChannel
6
7
  sailthru_email = edx_ace.channel.sailthru:SailthruEmailChannel
7
-