openedx-webhooks 22.0.0__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.
- openedx_webhooks/__init__.py +7 -0
- openedx_webhooks/admin.py +40 -0
- openedx_webhooks/apps.py +130 -0
- openedx_webhooks/filters.py +2276 -0
- openedx_webhooks/migrations/0001_initial.py +30 -0
- openedx_webhooks/migrations/0002_webfilter.py +37 -0
- openedx_webhooks/migrations/0003_auto_20230607_1246.py +23 -0
- openedx_webhooks/migrations/0004_auto_20230613_1033.py +28 -0
- openedx_webhooks/migrations/0005_alter_webfilter_event.py +18 -0
- openedx_webhooks/migrations/0006_alter_webfilter_event.py +18 -0
- openedx_webhooks/migrations/0007_alter_webfilter_event.py +18 -0
- openedx_webhooks/migrations/0008_verawood.py +51 -0
- openedx_webhooks/migrations/__init__.py +0 -0
- openedx_webhooks/models.py +185 -0
- openedx_webhooks/receivers.py +434 -0
- openedx_webhooks/settings/__init__.py +0 -0
- openedx_webhooks/settings/cms.py +21 -0
- openedx_webhooks/settings/common.py +191 -0
- openedx_webhooks/settings/test.py +10 -0
- openedx_webhooks/urls.py +10 -0
- openedx_webhooks/utils.py +124 -0
- openedx_webhooks-22.0.0.dist-info/METADATA +415 -0
- openedx_webhooks-22.0.0.dist-info/RECORD +27 -0
- openedx_webhooks-22.0.0.dist-info/WHEEL +5 -0
- openedx_webhooks-22.0.0.dist-info/entry_points.txt +5 -0
- openedx_webhooks-22.0.0.dist-info/licenses/LICENSE.txt +674 -0
- openedx_webhooks-22.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utilities used by Open edX Events Receivers.
|
|
3
|
+
"""
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
from collections.abc import MutableMapping
|
|
7
|
+
from typing import Any, Union
|
|
8
|
+
|
|
9
|
+
import requests
|
|
10
|
+
from attrs import fields, has
|
|
11
|
+
from opaque_keys import OpaqueKey
|
|
12
|
+
from xblock.fields import ScopeIds
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
SENSITIVE_FIELDS = frozenset({
|
|
17
|
+
'password', 'password1', 'password2', 'old_password', 'new_password',
|
|
18
|
+
'token', 'id_token', 'access_token', 'refresh_token',
|
|
19
|
+
'client_secret', 'authorization', 'cookie', 'csrfmiddlewaretoken',
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def redact_sensitive_fields(value):
|
|
24
|
+
"""Remove credentials recursively without changing the caller's objects."""
|
|
25
|
+
if isinstance(value, dict):
|
|
26
|
+
return {
|
|
27
|
+
key: redact_sensitive_fields(item) for key, item in value.items()
|
|
28
|
+
if not isinstance(key, str) or key.lower() not in SENSITIVE_FIELDS
|
|
29
|
+
}
|
|
30
|
+
if isinstance(value, (list, tuple)):
|
|
31
|
+
return [redact_sensitive_fields(item) for item in value]
|
|
32
|
+
return value
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def send(url, payload, www_form_urlencoded: bool = False):
|
|
36
|
+
"""
|
|
37
|
+
Dispatch the payload to the webhook url, return the response and catch exceptions.
|
|
38
|
+
"""
|
|
39
|
+
payload = redact_sensitive_fields(payload)
|
|
40
|
+
if www_form_urlencoded:
|
|
41
|
+
headers = {'Content-type': 'application/x-www-form-urlencoded', 'Accept': 'text/plain'}
|
|
42
|
+
payload = flatten_dict(payload)
|
|
43
|
+
data = payload
|
|
44
|
+
else:
|
|
45
|
+
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
|
|
46
|
+
data = json.dumps(payload, default=str)
|
|
47
|
+
|
|
48
|
+
r = requests.post(url, data=data, headers=headers, timeout=10)
|
|
49
|
+
|
|
50
|
+
return r
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def flatten_dict(dictionary, parent_key="", sep="_"):
|
|
54
|
+
"""
|
|
55
|
+
Generate a flatten dictionary-like object.
|
|
56
|
+
|
|
57
|
+
Taken from:
|
|
58
|
+
https://stackoverflow.com/a/6027615/16823624
|
|
59
|
+
"""
|
|
60
|
+
items = []
|
|
61
|
+
for key, value in dictionary.items():
|
|
62
|
+
key = str(key)
|
|
63
|
+
new_key = parent_key + sep + key if parent_key else key
|
|
64
|
+
if isinstance(value, MutableMapping):
|
|
65
|
+
items.extend(flatten_dict(value, new_key, sep=sep).items())
|
|
66
|
+
else:
|
|
67
|
+
items.append((new_key, str(value)))
|
|
68
|
+
return dict(items)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def value_serializer(inst, field, value): # pylint: disable=unused-argument
|
|
72
|
+
"""
|
|
73
|
+
Serialize values for attr function.
|
|
74
|
+
"""
|
|
75
|
+
return object_serializer(value)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def scope_ids_serializer(o):
|
|
79
|
+
"""
|
|
80
|
+
Serialize instances of ScopeId.
|
|
81
|
+
"""
|
|
82
|
+
return {
|
|
83
|
+
"block_type": o.block_type,
|
|
84
|
+
"def_id": str(o.def_id),
|
|
85
|
+
"usage_id": str(o.usage_id),
|
|
86
|
+
"user_id": o.user_id,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def object_serializer(o, depth=0) -> Union[dict, Any]:
|
|
91
|
+
"""
|
|
92
|
+
Serialize an arbitrary object as a json-serializable dict.
|
|
93
|
+
"""
|
|
94
|
+
if depth > 15:
|
|
95
|
+
return "! Depth limit reached !"
|
|
96
|
+
# First serialize scalar objects
|
|
97
|
+
if isinstance(o, (int, float, str)) or o is None:
|
|
98
|
+
return o
|
|
99
|
+
# ScopeIds is a class present in block structures
|
|
100
|
+
elif isinstance(o, ScopeIds):
|
|
101
|
+
return scope_ids_serializer(o)
|
|
102
|
+
elif isinstance(o, OpaqueKey):
|
|
103
|
+
return str(o)
|
|
104
|
+
if isinstance(o, (list, tuple, set)):
|
|
105
|
+
return [object_serializer(item, depth + 1) for item in o]
|
|
106
|
+
# Now serialize dict-like objects
|
|
107
|
+
return_value = {}
|
|
108
|
+
if isinstance(o, dict):
|
|
109
|
+
dict_values = o.copy()
|
|
110
|
+
elif has(type(o)):
|
|
111
|
+
dict_values = {field.name: getattr(o, field.name) for field in fields(type(o))}
|
|
112
|
+
elif hasattr(o, "__dict__"):
|
|
113
|
+
dict_values = o.__dict__.copy()
|
|
114
|
+
# if it is not a dict and cannot be converted to a dict, try to stringify it.
|
|
115
|
+
elif hasattr(o, "__str__"):
|
|
116
|
+
return str(o)
|
|
117
|
+
else:
|
|
118
|
+
return f"Unserializable {type(o)}"
|
|
119
|
+
for key, value in dict_values.items():
|
|
120
|
+
key = str(key)
|
|
121
|
+
# Hide private fields and credentials, including on nested objects.
|
|
122
|
+
if not key.startswith("_") and key.lower() not in SENSITIVE_FIELDS:
|
|
123
|
+
return_value[key] = object_serializer(value, depth + 1)
|
|
124
|
+
return return_value
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openedx_webhooks
|
|
3
|
+
Version: 22.0.0
|
|
4
|
+
Summary: Webhooks for Open edX
|
|
5
|
+
Home-page: https://github.com/aulasneo/openedx-webhooks
|
|
6
|
+
Author: Aulasneo
|
|
7
|
+
Author-email: andres@aulasneo.com
|
|
8
|
+
License: AGPL 3.0
|
|
9
|
+
Keywords: Openedx webhooks
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Framework :: Django
|
|
12
|
+
Classifier: Framework :: Django :: 5.2
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
|
15
|
+
Classifier: Natural Language :: English
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE.txt
|
|
21
|
+
Requires-Dist: Django<5.3,>=5.2
|
|
22
|
+
Requires-Dist: attrs
|
|
23
|
+
Requires-Dist: django-model-utils
|
|
24
|
+
Requires-Dist: edx_opaque_keys
|
|
25
|
+
Requires-Dist: openedx-events<12,>=11.2.0
|
|
26
|
+
Requires-Dist: openedx-filters<4,>=3.4.1
|
|
27
|
+
Requires-Dist: requests
|
|
28
|
+
Requires-Dist: xblock
|
|
29
|
+
Dynamic: author
|
|
30
|
+
Dynamic: author-email
|
|
31
|
+
Dynamic: classifier
|
|
32
|
+
Dynamic: description
|
|
33
|
+
Dynamic: description-content-type
|
|
34
|
+
Dynamic: home-page
|
|
35
|
+
Dynamic: keywords
|
|
36
|
+
Dynamic: license
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
Dynamic: requires-dist
|
|
39
|
+
Dynamic: requires-python
|
|
40
|
+
Dynamic: summary
|
|
41
|
+
|
|
42
|
+
# Open edX Webhooks
|
|
43
|
+
|
|
44
|
+
[](https://pypi.python.org/pypi/openedx-webhooks/)
|
|
45
|
+
[](https://github.com/aulasneo/openedx-webhooks/actions)
|
|
46
|
+
[](https://codecov.io/github/aulasneo/openedx-webhooks?branch=main)
|
|
47
|
+
[](https://github.com/aulasneo/openedx-webhooks)
|
|
48
|
+
[](https://pypi.python.org/pypi/openedx-webhooks/)
|
|
49
|
+
[](https://github.com/aulasneo/openedx-webhooks/blob/main/LICENSE.txt)
|
|
50
|
+

|
|
51
|
+
|
|
52
|
+
## Purpose
|
|
53
|
+
|
|
54
|
+
Webhooks for Open edX.
|
|
55
|
+
|
|
56
|
+
Current compatibility target: Open edX Verawood / Tutor 22.x.
|
|
57
|
+
|
|
58
|
+
This plugin implements a generic case of event handling that triggers a request
|
|
59
|
+
to a configurable URL when a signal is received.
|
|
60
|
+
|
|
61
|
+
## Getting Started
|
|
62
|
+
|
|
63
|
+
### Introduction
|
|
64
|
+
|
|
65
|
+
A `Webhook` triggers an HTTP POST request to a configurable URL when certain
|
|
66
|
+
events happen in the platform, including information relevant to the event. For
|
|
67
|
+
example, you can make the platform call an API when a user logs in, including
|
|
68
|
+
the user ID and email to connect to another application.
|
|
69
|
+
|
|
70
|
+
A `Webfilter` is a special case of webhook that can also modify data and/or
|
|
71
|
+
interrupt the process. For example, after the user login event you can update
|
|
72
|
+
the user full name or prevent the user from logging in.
|
|
73
|
+
|
|
74
|
+
### Deploying
|
|
75
|
+
|
|
76
|
+
To install this plugin into an Open edX instance installed by Tutor, add this
|
|
77
|
+
line to the `OPENEDX_EXTRA_PIP_REQUIREMENTS` list in `config.yml`.
|
|
78
|
+
|
|
79
|
+
```yaml
|
|
80
|
+
OPENEDX_EXTRA_PIP_REQUIREMENTS:
|
|
81
|
+
- openedx-webhooks
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
This release targets Open edX Verawood and Tutor 22.x, which use Python 3.12.
|
|
85
|
+
|
|
86
|
+
After rebuilding the Open edX image with the updated package, run migrations
|
|
87
|
+
before restarting LMS, Studio, and their workers:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
tutor {dev|local|k8s} exec lms ./manage.py lms migrate
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
This plugin now registers extension points in both LMS and CMS. Webhook and
|
|
94
|
+
webfilter configuration is still managed through Django admin and the same
|
|
95
|
+
database tables.
|
|
96
|
+
|
|
97
|
+
Migration `0008_verawood` adds the current event choices and corrects ten old
|
|
98
|
+
filter names that did not match their handlers. Existing URLs and controls are
|
|
99
|
+
preserved. Corrected configurations that previously never fired will now become
|
|
100
|
+
active; review enabled endpoints before upgrading. The migration does not convert
|
|
101
|
+
legacy instructor dashboard customizations into MFE tabs. Keep package version 21
|
|
102
|
+
for Ulmo deployments.
|
|
103
|
+
|
|
104
|
+
## Configuring
|
|
105
|
+
|
|
106
|
+
A new section named `OPENEDX_WEBHOOKS` is available in the LMS Django admin
|
|
107
|
+
site. It contains two subsections: `Webhooks` and `Webfilters`. Add a new
|
|
108
|
+
webhook to define the URLs that will be called after each event is received.
|
|
109
|
+
More than one URL can be configured for each event. In that case, all URLs are
|
|
110
|
+
called.
|
|
111
|
+
|
|
112
|
+
### Configuring Webhooks
|
|
113
|
+
|
|
114
|
+
The `Webhooks` Django admin panel has the following settings:
|
|
115
|
+
|
|
116
|
+
- Event: Choose from the list the event that will trigger the webhook.
|
|
117
|
+
- Webhook URL: URL to call. Get it from your webhook processor.
|
|
118
|
+
- Enabled: Click to enable the webhook.
|
|
119
|
+
- Use WWW form encoding: When enabled, the data will be passed in web form
|
|
120
|
+
format. If disabled, data will be passed in JSON format.
|
|
121
|
+
|
|
122
|
+
### Configuring Webfilters
|
|
123
|
+
|
|
124
|
+
The `Webfilters` Django admin panel has the following settings:
|
|
125
|
+
|
|
126
|
+
- Description: Add a description for this webhook for reference.
|
|
127
|
+
- Event: Choose from the list the event that will trigger the webhook.
|
|
128
|
+
- Webhook URL: URL to call. Get it from your webhook processor.
|
|
129
|
+
- Enabled: Click to enable the webhook.
|
|
130
|
+
- Disable filtering: If enabled, the data passed to the web filter will not be
|
|
131
|
+
modified even if the response includes any update.
|
|
132
|
+
- Disable halting: If enabled, the process will not be interrupted even if the
|
|
133
|
+
response includes an exception setting.
|
|
134
|
+
- Halt on 4xx: Interrupt the process if the call to the URL returns any 4xx
|
|
135
|
+
error code.
|
|
136
|
+
- Redirect on 4xx: Include a URL to redirect in case of a 4xx response, if the
|
|
137
|
+
event supports redirection.
|
|
138
|
+
- Halt on 5xx: Interrupt the process if the call to the URL returns any 5xx
|
|
139
|
+
error code.
|
|
140
|
+
- Redirect on 5xx: Include a URL to redirect in case of a 5xx response, if the
|
|
141
|
+
event supports redirection.
|
|
142
|
+
- Halt on request exception: Interrupt the process if the call to the URL
|
|
143
|
+
results in a connection error such as a timeout.
|
|
144
|
+
- Redirect on request exception: Include a URL to redirect in case of a
|
|
145
|
+
connection error, if the event supports redirection.
|
|
146
|
+
- Use WWW form encoding: When enabled, the data will be passed in web form
|
|
147
|
+
format. If disabled, data will be passed in JSON format.
|
|
148
|
+
|
|
149
|
+
## Receiving Data
|
|
150
|
+
|
|
151
|
+
Both webhooks and webfilters trigger POST requests to the configured URL. The
|
|
152
|
+
payload includes data relevant to the event that triggered the call. In all
|
|
153
|
+
cases, the payload includes an `event_metadata` key with at least the event
|
|
154
|
+
type and the date and time in UTC format. Other keys depend on the event. For
|
|
155
|
+
example, login events usually include `user` and `profile` keys with details of
|
|
156
|
+
the user logging in.
|
|
157
|
+
|
|
158
|
+
Signal webhooks retain their existing envelope: the data key is the fully
|
|
159
|
+
qualified upstream data class, such as
|
|
160
|
+
`openedx_events.authz.data.RoleAssignmentData`, alongside `event_metadata`.
|
|
161
|
+
Webfilters use argument-specific keys such as `user` and `profile`.
|
|
162
|
+
|
|
163
|
+
Credential fields (including passwords and password hashes) are omitted from
|
|
164
|
+
outgoing payloads. Remote model updates cannot change primary keys, relationships,
|
|
165
|
+
passwords, or account privilege flags. Other personal data is intentionally sent
|
|
166
|
+
to configured endpoints; restrict configuration access to trusted administrators.
|
|
167
|
+
|
|
168
|
+
If the `Use WWW form encoding` option is enabled, the data is passed as plain
|
|
169
|
+
key-value pairs in form encoding. The structure is flattened and the key names
|
|
170
|
+
are concatenated. For example, a login event includes `user_id`,
|
|
171
|
+
`user_email`, `event_metadata_time`, and similar fields.
|
|
172
|
+
|
|
173
|
+
## Responding to Webfilters
|
|
174
|
+
|
|
175
|
+
The webhook processor should respond to a webfilter with a JSON structure and a
|
|
176
|
+
successful status code (`200`). The response can be empty or can have one or
|
|
177
|
+
both of these keys:
|
|
178
|
+
|
|
179
|
+
- `data`
|
|
180
|
+
- `exception`
|
|
181
|
+
|
|
182
|
+
### Updating Data
|
|
183
|
+
|
|
184
|
+
The corresponding objects are updated with the values returned inside the
|
|
185
|
+
`data` key. Only keys present in the response are updated. Other keys remain
|
|
186
|
+
unchanged in the original data structures. To disable data updates, set
|
|
187
|
+
`Disable Filtering` in the webfilter configuration in the Django admin panel.
|
|
188
|
+
|
|
189
|
+
For example, to change the full name of a user at registration time, respond to
|
|
190
|
+
the `Student Registration Requested` webfilter with this data:
|
|
191
|
+
|
|
192
|
+
```json
|
|
193
|
+
{
|
|
194
|
+
"data": {
|
|
195
|
+
"form_data": {
|
|
196
|
+
"name": "New Name"
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Interrupting Execution
|
|
203
|
+
|
|
204
|
+
To stop the process, add a JSON object as the value for the `exception` key.
|
|
205
|
+
This object must have only one key-value pair: the key is the name of the
|
|
206
|
+
exception to raise, and the value can be either a string representing the
|
|
207
|
+
message to show or another JSON object with more data.
|
|
208
|
+
|
|
209
|
+
For example, to prevent a user from registering, respond to the `Student
|
|
210
|
+
Registration Requested` webfilter with this data:
|
|
211
|
+
|
|
212
|
+
```json
|
|
213
|
+
{
|
|
214
|
+
"exception": {
|
|
215
|
+
"PreventRegistration": "Not allowed to register"
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
To prevent a webfilter from stopping the process, set `Disable halting` in the
|
|
221
|
+
webfilter configuration in the Django admin panel.
|
|
222
|
+
|
|
223
|
+
Check each function documentation to see the list of available values and
|
|
224
|
+
exceptions.
|
|
225
|
+
|
|
226
|
+
## Handling Multiple Events
|
|
227
|
+
|
|
228
|
+
If you set more than one webhook or webfilter for the same event, all of them
|
|
229
|
+
are triggered. The responses of all webfilters are combined into one data
|
|
230
|
+
structure and used to update the objects. If more than one webfilter processor
|
|
231
|
+
includes data for the same key, the last one overrides the previous ones.
|
|
232
|
+
|
|
233
|
+
Endpoints run in ascending configuration ID order. Signal delivery failures are
|
|
234
|
+
logged and delivery continues to the other endpoints. Delivery is synchronous,
|
|
235
|
+
with ten-second connection and response-read timeouts, and has no persistent
|
|
236
|
+
retries. These timeouts are not a total request deadline; an endpoint that keeps
|
|
237
|
+
sending data can block delivery for longer than ten seconds. Webfilters
|
|
238
|
+
retain their configured halting behavior; filters with no upstream exception
|
|
239
|
+
cannot halt through the transport-error controls.
|
|
240
|
+
|
|
241
|
+
## Developing
|
|
242
|
+
|
|
243
|
+
More information about available signals can be found in the [events
|
|
244
|
+
documentation](https://docs.openedx.org/projects/openedx-events/en/stable/reference/events.html)
|
|
245
|
+
and the [filters
|
|
246
|
+
documentation](https://docs.openedx.org/projects/openedx-filters/en/stable/reference/filters.html).
|
|
247
|
+
|
|
248
|
+
Verawood compatibility in this package is aligned with:
|
|
249
|
+
|
|
250
|
+
- `openedx-filters==3.4.1`
|
|
251
|
+
- `openedx-events==11.2.0`
|
|
252
|
+
|
|
253
|
+
The runtime is Python 3.12 with Django 5.2; tests pin Django 5.2.13 from the
|
|
254
|
+
Verawood platform requirements.
|
|
255
|
+
|
|
256
|
+
New filters are `InstructorDashboardTabsRequested`,
|
|
257
|
+
`AccountSettingsReadOnlyFieldsRequested`, and `GradeEventContextRequested`.
|
|
258
|
+
New `ROLE_ASSIGNMENT_CREATED` and `ROLE_ASSIGNMENT_DELETED` webhook events are
|
|
259
|
+
registered in both LMS and Studio. Existing learning and authoring signal names
|
|
260
|
+
and receiver arguments are unchanged. Discussion payloads now include `enabled`;
|
|
261
|
+
forum IDs are strings and several forum fields may be null.
|
|
262
|
+
|
|
263
|
+
For the new instructor dashboard, configure `InstructorDashboardTabsRequested`
|
|
264
|
+
and return `{"data": {"tabs": [{"tab_id": "reports", "title": "Reports", "url": "https://example.com/reports"}]}}`.
|
|
265
|
+
This replaces the tabs list (including with an empty list); include any existing
|
|
266
|
+
tabs you wish to keep. Its filter identifier is
|
|
267
|
+
`org.openedx.learning.instructor.dashboard.tabs.requested.v1` in the pinned source,
|
|
268
|
+
despite the `tabs.generated.v1` identifier mentioned in the release notes.
|
|
269
|
+
Legacy `InstructorDashboardRenderStarted` remains available only when the legacy
|
|
270
|
+
dashboard is enabled. Frontend plugin configuration is still needed to render
|
|
271
|
+
custom content inside the MFE.
|
|
272
|
+
|
|
273
|
+
For account restrictions, return `{"data": {"readonly_fields": ["name", "email"]}}`.
|
|
274
|
+
These names are added to existing restrictions; the pipeline retains a Python set.
|
|
275
|
+
For grade event enrichment, return `{"data": {"context": {"source": "crm"}}}`.
|
|
276
|
+
The context is merged while retaining the original user and course identifiers.
|
|
277
|
+
The pinned platform has no caller for `GradeEventContextRequested`, so it requires
|
|
278
|
+
an upstream/custom integration to fire. `AccountSettingsRenderStarted` and
|
|
279
|
+
`CourseEnrollmentQuerysetRequested` likewise have no callers in stock Verawood;
|
|
280
|
+
they remain registered for compatibility with custom integrations.
|
|
281
|
+
|
|
282
|
+
See the [Verawood compatibility and code review](docs/verawood-review.md) for
|
|
283
|
+
the upstream comparison, fixed defects, and deployment validation limits.
|
|
284
|
+
|
|
285
|
+
Existing recent coverage retained from prior releases includes:
|
|
286
|
+
|
|
287
|
+
- Filter: `AccountSettingsRenderStarted`
|
|
288
|
+
- Signals:
|
|
289
|
+
- `EXTERNAL_GRADER_SCORE_SUBMITTED`
|
|
290
|
+
- `LIBRARY_BLOCK_PUBLISHED`
|
|
291
|
+
- `LIBRARY_CONTAINER_CREATED`
|
|
292
|
+
- `LIBRARY_CONTAINER_UPDATED`
|
|
293
|
+
- `LIBRARY_CONTAINER_DELETED`
|
|
294
|
+
- `LIBRARY_CONTAINER_PUBLISHED`
|
|
295
|
+
- `COURSE_IMPORT_COMPLETED`
|
|
296
|
+
|
|
297
|
+
### Adding More Filters
|
|
298
|
+
|
|
299
|
+
From version to version, new filters are added to Open edX. The complete list
|
|
300
|
+
of filters and their definitions can be found in `filters.py` in the
|
|
301
|
+
[openedx-filters repository](https://github.com/openedx/openedx-filters/blob/v3.4.1/openedx_filters/learning/filters.py).
|
|
302
|
+
|
|
303
|
+
To add a new filter, create the filter class handler in `filters.py`
|
|
304
|
+
containing the `run_filter` function. Then add a block in `common.py` linking
|
|
305
|
+
the signal identifier with the function. `run_filter` must always return a
|
|
306
|
+
dict, which can be empty.
|
|
307
|
+
|
|
308
|
+
### Adding New Event Hooks
|
|
309
|
+
|
|
310
|
+
From version to version, new event producers are added to Open edX. The
|
|
311
|
+
complete list of events and their definitions can be found in `signals.py` in
|
|
312
|
+
different folders in the
|
|
313
|
+
[openedx-events repository](https://github.com/openedx/openedx-events/tree/v11.2.0/openedx_events),
|
|
314
|
+
depending on their category.
|
|
315
|
+
|
|
316
|
+
To add a new event hook, add the signal to the `signals` dict in `apps.py`.
|
|
317
|
+
Then add the corresponding block to `receivers.py`.
|
|
318
|
+
|
|
319
|
+
### One-Time Setup
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
# Clone the repository
|
|
323
|
+
git clone git@github.com:aulasneo/openedx-webhooks.git
|
|
324
|
+
cd openedx-webhooks
|
|
325
|
+
|
|
326
|
+
# Set up a virtualenv with the same name as the repo and activate it
|
|
327
|
+
# Here's how you might do that if you have virtualenvwrapper setup.
|
|
328
|
+
mkvirtualenv -p python3.12 openedx-webhooks
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Every Time You Develop Something In This Repo
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
# Activate the virtualenv
|
|
335
|
+
# Here's how you might do that if you're using virtualenvwrapper.
|
|
336
|
+
workon openedx-webhooks
|
|
337
|
+
|
|
338
|
+
# Grab the latest code
|
|
339
|
+
git checkout main
|
|
340
|
+
git pull
|
|
341
|
+
|
|
342
|
+
# Install/update the dev requirements
|
|
343
|
+
make requirements
|
|
344
|
+
|
|
345
|
+
# Run the tests and quality checks (to verify the status before you make any changes)
|
|
346
|
+
make validate
|
|
347
|
+
|
|
348
|
+
# Make a new branch for your changes
|
|
349
|
+
git checkout -b <your_github_username>/<short_description>
|
|
350
|
+
|
|
351
|
+
# Using your favorite editor, edit the code to make your change.
|
|
352
|
+
vim ...
|
|
353
|
+
|
|
354
|
+
# Run your new tests
|
|
355
|
+
pytest ./path/to/new/tests
|
|
356
|
+
|
|
357
|
+
# Run all the tests and quality checks
|
|
358
|
+
make validate
|
|
359
|
+
|
|
360
|
+
# Commit all your changes
|
|
361
|
+
git commit ...
|
|
362
|
+
git push
|
|
363
|
+
|
|
364
|
+
# Open a PR and ask for review.
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## Releasing
|
|
368
|
+
|
|
369
|
+
PyPI publishing uses GitHub Actions with PyPI trusted publishing. The release
|
|
370
|
+
workflow no longer uses a long-lived PyPI API token. Configure the project's
|
|
371
|
+
trusted publisher in PyPI before cutting a GitHub release.
|
|
372
|
+
|
|
373
|
+
Release automation expects tags in the format `release/v<version>`, for example
|
|
374
|
+
`release/v22.0.0`. Pushing one of those tags from a commit contained in `main`
|
|
375
|
+
creates a GitHub release, and the publish workflow can also be run manually
|
|
376
|
+
from the Actions tab when needed.
|
|
377
|
+
|
|
378
|
+
## Getting Help
|
|
379
|
+
|
|
380
|
+
If you need any help, email [info@aulasneo.com](mailto:info@aulasneo.com).
|
|
381
|
+
|
|
382
|
+
## More Help
|
|
383
|
+
|
|
384
|
+
If you're having trouble, there are discussion forums at
|
|
385
|
+
<https://discuss.openedx.org> where you can connect with others in the
|
|
386
|
+
community.
|
|
387
|
+
|
|
388
|
+
Real-time conversations are on Slack. You can request a [Slack
|
|
389
|
+
invitation](https://openedx.org/slack), then join the [community Slack
|
|
390
|
+
workspace](https://openedx.slack.com/).
|
|
391
|
+
|
|
392
|
+
For anything non-trivial, the best path is to open an issue in this repository
|
|
393
|
+
with as many details about the issue as possible:
|
|
394
|
+
|
|
395
|
+
<https://github.com/aulasneo/openedx-webhooks/issues>
|
|
396
|
+
|
|
397
|
+
For more information about these options, see the [Getting
|
|
398
|
+
Help](https://openedx.org/getting-help) page.
|
|
399
|
+
|
|
400
|
+
## License
|
|
401
|
+
|
|
402
|
+
The code in this repository is licensed under the AGPL 3.0 unless otherwise
|
|
403
|
+
noted.
|
|
404
|
+
|
|
405
|
+
See [LICENSE.txt](LICENSE.txt) for details.
|
|
406
|
+
|
|
407
|
+
## Contributing
|
|
408
|
+
|
|
409
|
+
Contributions are very welcome. If you want to contribute to this project,
|
|
410
|
+
please feel free to open an issue.
|
|
411
|
+
|
|
412
|
+
## Reporting Security Issues
|
|
413
|
+
|
|
414
|
+
Please do not report security issues in public. Email
|
|
415
|
+
[operations@aulasneo.com](mailto:operations@aulasneo.com).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
openedx_webhooks/__init__.py,sha256=zTVzmaTQLlZKPuQuheztWCOK4MZ4vd-q2ljUlY5FLUI,148
|
|
2
|
+
openedx_webhooks/admin.py,sha256=aCECzm0pw-nHauP80QSBWIFE6RWgjUb7raHxJQJgmgE,846
|
|
3
|
+
openedx_webhooks/apps.py,sha256=mOdSBRW-ZAi3VrCvQ8ZwfhZf2pInGWgh0sSYxRRf1Hk,4064
|
|
4
|
+
openedx_webhooks/filters.py,sha256=XZF8zkY2FVeJmmS0QwNqY27CX2C9wCb0E4m8nR5LpM0,79911
|
|
5
|
+
openedx_webhooks/models.py,sha256=45DZNCcGcGG92gLBYMGS5Cm2pyJbQSWyiux9-DIGWDE,5109
|
|
6
|
+
openedx_webhooks/receivers.py,sha256=ZJ57aaJ9Bx9MwuZ-MNQfKjfg1C1LntGLsbNuGO65wzQ,15589
|
|
7
|
+
openedx_webhooks/urls.py,sha256=viSWPpXc_TE9KcAjgJW4De6m26ficeMyDGbZmKRL4CE,325
|
|
8
|
+
openedx_webhooks/utils.py,sha256=XUxxSYiK3X2_g4zE26unDqZOY-qPWBm6_FNT2Pnzz_M,3959
|
|
9
|
+
openedx_webhooks/migrations/0001_initial.py,sha256=oVTL4tXM6SNWA2nlPaRbuPOsybMCxeS9GpL-VMK3R0o,1737
|
|
10
|
+
openedx_webhooks/migrations/0002_webfilter.py,sha256=mNLlME_gAT9yWN-9t5BunPTk5XmvqRqIVuZBVysRdCE,3228
|
|
11
|
+
openedx_webhooks/migrations/0003_auto_20230607_1246.py,sha256=VgVCO3me9Fx3Hu89ev97YnxrZfhbZwftZ8KXsgTYHUE,796
|
|
12
|
+
openedx_webhooks/migrations/0004_auto_20230613_1033.py,sha256=oIQTd1T4LqVBmNCWZKuKqNzhp0hKg0AO7lcJjmmMZmc,1094
|
|
13
|
+
openedx_webhooks/migrations/0005_alter_webfilter_event.py,sha256=CRCMjQKFmnupjw_FGHiMoI44_kRFfB685dl1XH-kE4E,446
|
|
14
|
+
openedx_webhooks/migrations/0006_alter_webfilter_event.py,sha256=fYMTYMzvnjsCuJ7tl2pRtA-YzOKYn2m8_hxegYWVOOM,1199
|
|
15
|
+
openedx_webhooks/migrations/0007_alter_webfilter_event.py,sha256=WlFuUGwywa41tRPFCxmdNxKABEZHf8S0MIMtrJ-VhQk,1050
|
|
16
|
+
openedx_webhooks/migrations/0008_verawood.py,sha256=ZswxHmnDGkTP-9Bc6mik5r95F04S28OexIqdsyp8ufo,7534
|
|
17
|
+
openedx_webhooks/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
openedx_webhooks/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
openedx_webhooks/settings/cms.py,sha256=QEtiDk1nHq6_Quk7E7jap3GH9XWFD4zdZI4qctRU7K8,478
|
|
20
|
+
openedx_webhooks/settings/common.py,sha256=H06aCvRa4quxbe-I70f9354ZF24i-jbX1uX5uUxu2OM,7098
|
|
21
|
+
openedx_webhooks/settings/test.py,sha256=nJXOG4XNMs9VRdHmnL4TUeuONwDXGsZv5hWRSz-eU38,246
|
|
22
|
+
openedx_webhooks-22.0.0.dist-info/licenses/LICENSE.txt,sha256=QTW2QN7q3XszgUAXm9Dzgtu5LXYKbR1SGnqMa7ufEuY,35139
|
|
23
|
+
openedx_webhooks-22.0.0.dist-info/METADATA,sha256=DhAxmvWKglX1TNnhskvmrLnw1qFeLBKu1qoZhN31U4Y,16244
|
|
24
|
+
openedx_webhooks-22.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
25
|
+
openedx_webhooks-22.0.0.dist-info/entry_points.txt,sha256=kuvSNrY3saVbifd-sYukNPq5XP7vfLeUHqTKMV-Vq3k,129
|
|
26
|
+
openedx_webhooks-22.0.0.dist-info/top_level.txt,sha256=LLj81pdmwEFx-XJ-kcfekUK66Hi43GDz7GeANr_PfWo,17
|
|
27
|
+
openedx_webhooks-22.0.0.dist-info/RECORD,,
|