edx-enterprise-data 9.7.11__py3-none-any.whl → 9.9.1__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_enterprise_data-9.7.11.dist-info → edx_enterprise_data-9.9.1.dist-info}/METADATA +1 -1
- {edx_enterprise_data-9.7.11.dist-info → edx_enterprise_data-9.9.1.dist-info}/RECORD +13 -11
- enterprise_data/__init__.py +1 -1
- enterprise_data/api/v1/serializers.py +14 -0
- enterprise_data/api/v1/views/enterprise_learner.py +47 -6
- enterprise_data/clients.py +27 -0
- enterprise_data/migrations/0047_enterpriseexecedlcmoduleperformance_avg_after_lo_score_and_more.py +28 -0
- enterprise_data/migrations/0048_alter_enterpriseexecedlcmoduleperformance_avg_after_lo_score_and_more.py +23 -0
- enterprise_data/models.py +3 -0
- enterprise_data/utils.py +19 -0
- {edx_enterprise_data-9.7.11.dist-info → edx_enterprise_data-9.9.1.dist-info}/LICENSE +0 -0
- {edx_enterprise_data-9.7.11.dist-info → edx_enterprise_data-9.9.1.dist-info}/WHEEL +0 -0
- {edx_enterprise_data-9.7.11.dist-info → edx_enterprise_data-9.9.1.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,14 @@
|
|
1
|
-
enterprise_data/__init__.py,sha256=
|
1
|
+
enterprise_data/__init__.py,sha256=C2aHImiEECgYsjhXVl_9wAosZE3Qd9wtT1_s7eAwDU0,123
|
2
2
|
enterprise_data/apps.py,sha256=aF6hZwDfI2oWj95tUTm_2ikHueQj-jLj-u0GrgzpsQI,414
|
3
|
-
enterprise_data/clients.py,sha256=
|
3
|
+
enterprise_data/clients.py,sha256=pBuCQOP7NYl264ZbMtZCRPCET1pg0by8w6PVlA-icKA,4995
|
4
4
|
enterprise_data/constants.py,sha256=uCKjfpdlMYFZJsAj3n9RMw4Cmg5_6s3NuwocO-fch3s,238
|
5
5
|
enterprise_data/filters.py,sha256=D2EiK12MMpBoz6eOUmTpoJEhj_sH7bA93NRRAdvkDVo,6163
|
6
|
-
enterprise_data/models.py,sha256=
|
6
|
+
enterprise_data/models.py,sha256=gbZl-ei0qe_l-BFeE0gWeyfWqmgi9X9LAF8MYOEfk2s,26555
|
7
7
|
enterprise_data/paginators.py,sha256=YPrC5TeXFt-ymenT2H8H2nCbDCnAzJQlH9kFPElRxWE,269
|
8
8
|
enterprise_data/renderers.py,sha256=qggCLZklL9ohVcLHLO1pSecPJSDCCR7e_-PVobl9Lj8,3063
|
9
9
|
enterprise_data/signals.py,sha256=8eqNPnlvmfsKf19lGWv5xTIuBgQIqR8EZSp9UYzC8Rc,1024
|
10
10
|
enterprise_data/urls.py,sha256=bqtKF5OEWEwrNmHG3os-pZNuNsmjlhxEqp7yM4TbPf4,243
|
11
|
-
enterprise_data/utils.py,sha256=
|
11
|
+
enterprise_data/utils.py,sha256=djGuQUnvT8HJMMV60CzcD5BWerPIwiK9uG26bhkpWMg,3518
|
12
12
|
enterprise_data/admin_analytics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
13
|
enterprise_data/admin_analytics/constants.py,sha256=-6uLAq5DUeA_rv5eUb9SeqlG3iVWV30qUS8asbK4430,160
|
14
14
|
enterprise_data/admin_analytics/data_loaders.py,sha256=b6RjEIxCol8ETQMY7QfwhqN9eEAvrUN_UldIG7rgsSY,736
|
@@ -30,7 +30,7 @@ enterprise_data/api/v0/serializers.py,sha256=dngZTk6DhRxApchQKCMp1B_c8aVnQtH0NCq
|
|
30
30
|
enterprise_data/api/v0/urls.py,sha256=vzJjqIo_S3AXWs9Us8XTaJc3FnxLbYzAkmLyuDQqum0,699
|
31
31
|
enterprise_data/api/v0/views.py,sha256=4RslZ4NZOU-844bnebEQ71ji2utRY7jEijqC45oQQD0,14380
|
32
32
|
enterprise_data/api/v1/__init__.py,sha256=1aAzAYU5hk-RW6cKUxa1645cbZMxn7GIZ7OMjWc9MKI,46
|
33
|
-
enterprise_data/api/v1/serializers.py,sha256=
|
33
|
+
enterprise_data/api/v1/serializers.py,sha256=DUhk8cP-JFQmbLR_JNGmmwis9Un5dyvd01kRdmxn8Lk,11866
|
34
34
|
enterprise_data/api/v1/urls.py,sha256=Wl1xLboPg-Lq1ZvjAWf51JKYkHlmx02Kpq1nwfDyS8s,4372
|
35
35
|
enterprise_data/api/v1/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
36
36
|
enterprise_data/api/v1/views/analytics_completions.py,sha256=esFbJ5q8ssnm2Mfbc3rZXtiGHF-MeM4KQ4Ft3N7wwHU,6260
|
@@ -39,7 +39,7 @@ enterprise_data/api/v1/views/analytics_enrollments.py,sha256=hw87VZ0hFWpwf3QEHFn
|
|
39
39
|
enterprise_data/api/v1/views/analytics_leaderboard.py,sha256=3dyo7_OhyGEEeibemBrRsUOo0jbM4LbDgV5gw3YnVig,4186
|
40
40
|
enterprise_data/api/v1/views/base.py,sha256=Kkmd5zgEBAhvwS_GoGXSK6lgbDNwSPioYn-QbnizI3w,3416
|
41
41
|
enterprise_data/api/v1/views/enterprise_admin.py,sha256=xxpb48cjuHkFJH-IQG0DwOvdH8RgyYD-aQjyfMMJNFg,9030
|
42
|
-
enterprise_data/api/v1/views/enterprise_learner.py,sha256=
|
42
|
+
enterprise_data/api/v1/views/enterprise_learner.py,sha256=noFAKFe1T8CXRSFt8aMajF-utPO4x2WK-kxdIYw5qE4,20601
|
43
43
|
enterprise_data/api/v1/views/enterprise_offers.py,sha256=VifxgqTLFLVw4extYPlHcN1N_yjXcsYsAlYEnAbpb10,1266
|
44
44
|
enterprise_data/cache/__init__.py,sha256=fiBUploll1kmDy2vCmnNpeZVTD4ewsgtRF14vVs0Rb4,1850
|
45
45
|
enterprise_data/cache/decorators.py,sha256=vLbXK9VSv-HzVkkXS1-TkuSMxudwyxz04WFsAXqmjuM,1273
|
@@ -106,6 +106,8 @@ enterprise_data/migrations/0040_auto_20240718_0536_squashed_0043_alter_enterpris
|
|
106
106
|
enterprise_data/migrations/0044_enterpriseexecedlcmoduleperformance.py,sha256=1aJ0wIu9K2cT3AJdBIHuNCUpJysPWmq_37Ucp5ntEO8,6122
|
107
107
|
enterprise_data/migrations/0045_alter_enterpriseexecedlcmoduleperformance_options_and_more.py,sha256=HJkY2OvuE39ciWgaEescd--ixQhTVrwzbL0_bvNofSo,876
|
108
108
|
enterprise_data/migrations/0046_enterprisegroupmembership.py,sha256=38qfi3EySDLrpP3gDuO_oUl_7rphGPZIrPsCuBhQ_dk,1766
|
109
|
+
enterprise_data/migrations/0047_enterpriseexecedlcmoduleperformance_avg_after_lo_score_and_more.py,sha256=_lHmsJozjV_JEWyUfHDKleOhd53594fOaRhz7M0z-nY,897
|
110
|
+
enterprise_data/migrations/0048_alter_enterpriseexecedlcmoduleperformance_avg_after_lo_score_and_more.py,sha256=Hjgjj1LwNSCno45EHECCBy5jKKb51Me61xo8ukQLIEI,740
|
109
111
|
enterprise_data/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
110
112
|
enterprise_data/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
111
113
|
enterprise_data/settings/test.py,sha256=4-flfrlf81AthGx9wTaT5PscyoOWyhsDDqbzBl-z7Eg,4191
|
@@ -173,8 +175,8 @@ enterprise_reporting/tests/test_send_enterprise_reports.py,sha256=WtL-RqGgu2x5PP
|
|
173
175
|
enterprise_reporting/tests/test_utils.py,sha256=Zt_TA0LVb-B6fQGkUkAKKVlUKKnQh8jnw1US1jKe7g8,9493
|
174
176
|
enterprise_reporting/tests/test_vertica_client.py,sha256=-R2yNCGUjRtoXwLMBloVFQkFYrJoo613VCr61gwI3kQ,140
|
175
177
|
enterprise_reporting/tests/utils.py,sha256=xms2LM7DV3wczXEfctOK1ddel1EE0J_YSr17UzbCDy4,1401
|
176
|
-
edx_enterprise_data-9.
|
177
|
-
edx_enterprise_data-9.
|
178
|
-
edx_enterprise_data-9.
|
179
|
-
edx_enterprise_data-9.
|
180
|
-
edx_enterprise_data-9.
|
178
|
+
edx_enterprise_data-9.9.1.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
|
179
|
+
edx_enterprise_data-9.9.1.dist-info/METADATA,sha256=ncWbp84mP_4B5WW0va6GldAOyH5AxZ-3j6XacbwQjWY,1569
|
180
|
+
edx_enterprise_data-9.9.1.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
181
|
+
edx_enterprise_data-9.9.1.dist-info/top_level.txt,sha256=f5F2kU-dob6MqiHJpgZkFzoCD5VMhsdpkTV5n9Tvq3I,59
|
182
|
+
edx_enterprise_data-9.9.1.dist-info/RECORD,,
|
enterprise_data/__init__.py
CHANGED
@@ -16,6 +16,7 @@ from enterprise_data.models import (
|
|
16
16
|
EnterpriseOffer,
|
17
17
|
EnterpriseSubsidyBudget,
|
18
18
|
)
|
19
|
+
from enterprise_data.utils import calculate_percentage_difference
|
19
20
|
|
20
21
|
|
21
22
|
class EnterpriseLearnerEnrollmentSerializer(serializers.ModelSerializer):
|
@@ -231,6 +232,7 @@ class EnterpriseExecEdLCModulePerformanceSerializer(serializers.ModelSerializer)
|
|
231
232
|
Serializer for EnterpriseExecEdLCModulePerformance model.
|
232
233
|
"""
|
233
234
|
extensions_requested = serializers.SerializerMethodField()
|
235
|
+
avg_lo_percentage_difference = serializers.SerializerMethodField()
|
234
236
|
|
235
237
|
class Meta:
|
236
238
|
model = EnterpriseExecEdLCModulePerformance
|
@@ -240,6 +242,18 @@ class EnterpriseExecEdLCModulePerformanceSerializer(serializers.ModelSerializer)
|
|
240
242
|
"""Return extensions_requested if not None, otherwise return 0"""
|
241
243
|
return obj.extensions_requested if obj.extensions_requested is not None else 0
|
242
244
|
|
245
|
+
def get_avg_lo_percentage_difference(self, obj):
|
246
|
+
"""
|
247
|
+
Return percentage difference between `avg_before_lo_score` and `avg_after_lo_score` if not None,
|
248
|
+
otherwise return None
|
249
|
+
"""
|
250
|
+
if obj.avg_before_lo_score is None or obj.avg_after_lo_score is None:
|
251
|
+
return None
|
252
|
+
return round(
|
253
|
+
calculate_percentage_difference(obj.avg_before_lo_score, obj.avg_after_lo_score),
|
254
|
+
2
|
255
|
+
)
|
256
|
+
|
243
257
|
|
244
258
|
class EnterpriseBudgetSerializer(serializers.ModelSerializer):
|
245
259
|
"""
|
@@ -8,6 +8,7 @@ from uuid import UUID
|
|
8
8
|
|
9
9
|
from rest_framework import filters, viewsets
|
10
10
|
from rest_framework.decorators import action
|
11
|
+
from rest_framework.exceptions import NotFound
|
11
12
|
from rest_framework.response import Response
|
12
13
|
|
13
14
|
from django.conf import settings
|
@@ -20,6 +21,7 @@ from django.utils import timezone
|
|
20
21
|
|
21
22
|
from enterprise_data.admin_analytics.database.utils import LOGGER
|
22
23
|
from enterprise_data.api.v1 import serializers
|
24
|
+
from enterprise_data.clients import EnterpriseApiClient
|
23
25
|
from enterprise_data.models import EnterpriseGroupMembership, EnterpriseLearner, EnterpriseLearnerEnrollment
|
24
26
|
from enterprise_data.paginators import EnterpriseEnrollmentsPagination
|
25
27
|
from enterprise_data.renderers import EnrollmentsCSVRenderer
|
@@ -176,12 +178,51 @@ class EnterpriseLearnerEnrollmentViewSet(EnterpriseViewSetMixin, viewsets.ReadOn
|
|
176
178
|
|
177
179
|
group_uuid = query_filters.get('group_uuid')
|
178
180
|
if group_uuid:
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
181
|
+
queryset = self.filter_by_group_uuid(queryset, group_uuid)
|
182
|
+
|
183
|
+
return queryset
|
184
|
+
|
185
|
+
def filter_by_group_uuid(self, queryset, group_uuid):
|
186
|
+
"""
|
187
|
+
Filters the queryset based on the provided group_uuid. If no records are found,
|
188
|
+
it attempts to fetch group learners from the API and filter the queryset again.
|
189
|
+
|
190
|
+
Args:
|
191
|
+
queryset (QuerySet): The initial queryset to filter.
|
192
|
+
group_uuid (str): The UUID of the group to filter by.
|
193
|
+
|
194
|
+
Returns:
|
195
|
+
QuerySet: The filtered queryset.
|
196
|
+
"""
|
197
|
+
flex_group_exists = EnterpriseGroupMembership.objects.filter(
|
198
|
+
enterprise_customer_user_id=OuterRef('enterprise_user_id'),
|
199
|
+
enterprise_group_uuid=group_uuid,
|
200
|
+
group_type='flex'
|
201
|
+
)
|
202
|
+
|
203
|
+
# First, filter the queryset using flex_group_exists
|
204
|
+
filtered_queryset = queryset.filter(Exists(flex_group_exists))
|
205
|
+
|
206
|
+
# If no records are found, attempt to fetch group_learners from the API
|
207
|
+
if not filtered_queryset.exists():
|
208
|
+
try:
|
209
|
+
enterprise_api_client = EnterpriseApiClient(
|
210
|
+
settings.BACKEND_SERVICE_EDX_OAUTH2_PROVIDER_URL,
|
211
|
+
settings.BACKEND_SERVICE_EDX_OAUTH2_KEY,
|
212
|
+
settings.BACKEND_SERVICE_EDX_OAUTH2_SECRET,
|
213
|
+
)
|
214
|
+
group_learners = enterprise_api_client.get_enterprise_group_learners(group_uuid)
|
215
|
+
if group_learners:
|
216
|
+
group_learners_ids = [learner['enterprise_customer_user_id'] for learner in group_learners]
|
217
|
+
queryset = queryset.filter(enterprise_user_id__in=group_learners_ids)
|
218
|
+
else:
|
219
|
+
LOGGER.warning(f"No group learners found for group: {group_uuid}")
|
220
|
+
raise NotFound(f"No learners found for group: {group_uuid}")
|
221
|
+
except (Exception) as e: # pylint: disable=broad-exception-caught
|
222
|
+
LOGGER.error("Failed to fetch group learners from API: %s", e)
|
223
|
+
queryset = queryset.none() # API call failed or unexpected error, return an empty queryset
|
224
|
+
else:
|
225
|
+
queryset = filtered_queryset
|
185
226
|
|
186
227
|
return queryset
|
187
228
|
|
enterprise_data/clients.py
CHANGED
@@ -101,3 +101,30 @@ class EnterpriseApiClient(OAuthAPIClient):
|
|
101
101
|
TieredCache.set_all_tiers(cache_key, data, DEFAULT_REPORTING_CACHE_TIMEOUT)
|
102
102
|
|
103
103
|
return data
|
104
|
+
|
105
|
+
def get_enterprise_group_learners(self, group_uuid):
|
106
|
+
"""
|
107
|
+
Get the learners associated with a given enterprise group.
|
108
|
+
|
109
|
+
Returns: list of learners or None if unable to retrieve or no learners exist
|
110
|
+
"""
|
111
|
+
LOGGER.info(f'[EnterpriseApiClient] getting learners for enterprise group:{group_uuid}')
|
112
|
+
url = urljoin(self.API_BASE_URL, f'enterprise-group/{group_uuid}/learners/')
|
113
|
+
all_learners = []
|
114
|
+
|
115
|
+
try:
|
116
|
+
while url:
|
117
|
+
response = self.get(url)
|
118
|
+
response.raise_for_status()
|
119
|
+
data = response.json()
|
120
|
+
all_learners.extend(data.get('results', []))
|
121
|
+
url = data.get('next') # Get the URL for the next page, if any
|
122
|
+
|
123
|
+
except (HTTPError, RequestException) as exc:
|
124
|
+
LOGGER.warning(
|
125
|
+
"[Data Overview Failure] Unable to retrieve Enterprise Group Learners details. "
|
126
|
+
f"Group: {group_uuid}, Exception: {exc}"
|
127
|
+
)
|
128
|
+
return None
|
129
|
+
|
130
|
+
return all_learners
|
enterprise_data/migrations/0047_enterpriseexecedlcmoduleperformance_avg_after_lo_score_and_more.py
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Generated by Django 4.2.15 on 2025-02-20 08:08
|
2
|
+
|
3
|
+
from django.db import migrations, models
|
4
|
+
|
5
|
+
|
6
|
+
class Migration(migrations.Migration):
|
7
|
+
|
8
|
+
dependencies = [
|
9
|
+
('enterprise_data', '0046_enterprisegroupmembership'),
|
10
|
+
]
|
11
|
+
|
12
|
+
operations = [
|
13
|
+
migrations.AddField(
|
14
|
+
model_name='enterpriseexecedlcmoduleperformance',
|
15
|
+
name='avg_after_lo_score',
|
16
|
+
field=models.DecimalField(decimal_places=6, max_digits=38, null=True),
|
17
|
+
),
|
18
|
+
migrations.AddField(
|
19
|
+
model_name='enterpriseexecedlcmoduleperformance',
|
20
|
+
name='avg_before_lo_score',
|
21
|
+
field=models.DecimalField(decimal_places=6, max_digits=38, null=True),
|
22
|
+
),
|
23
|
+
migrations.AddField(
|
24
|
+
model_name='enterpriseexecedlcmoduleperformance',
|
25
|
+
name='question_name',
|
26
|
+
field=models.CharField(max_length=500, null=True),
|
27
|
+
),
|
28
|
+
]
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Generated by Django 4.2.15 on 2025-02-25 08:17
|
2
|
+
|
3
|
+
from django.db import migrations, models
|
4
|
+
|
5
|
+
|
6
|
+
class Migration(migrations.Migration):
|
7
|
+
|
8
|
+
dependencies = [
|
9
|
+
('enterprise_data', '0047_enterpriseexecedlcmoduleperformance_avg_after_lo_score_and_more'),
|
10
|
+
]
|
11
|
+
|
12
|
+
operations = [
|
13
|
+
migrations.AlterField(
|
14
|
+
model_name='enterpriseexecedlcmoduleperformance',
|
15
|
+
name='avg_after_lo_score',
|
16
|
+
field=models.DecimalField(decimal_places=2, max_digits=38, null=True),
|
17
|
+
),
|
18
|
+
migrations.AlterField(
|
19
|
+
model_name='enterpriseexecedlcmoduleperformance',
|
20
|
+
name='avg_before_lo_score',
|
21
|
+
field=models.DecimalField(decimal_places=2, max_digits=38, null=True),
|
22
|
+
),
|
23
|
+
]
|
enterprise_data/models.py
CHANGED
@@ -558,6 +558,9 @@ class EnterpriseExecEdLCModulePerformance(models.Model):
|
|
558
558
|
discussion_forum_activities_completed_count = models.PositiveIntegerField(null=True)
|
559
559
|
discussion_forum_activities_total_count = models.PositiveIntegerField(null=True)
|
560
560
|
pass_grade = models.DecimalField(max_digits=38, decimal_places=2, null=True)
|
561
|
+
question_name = models.CharField(max_length=500, null=True)
|
562
|
+
avg_before_lo_score = models.DecimalField(max_digits=38, decimal_places=2, null=True)
|
563
|
+
avg_after_lo_score = models.DecimalField(max_digits=38, decimal_places=2, null=True)
|
561
564
|
|
562
565
|
def __str__(self):
|
563
566
|
"""
|
enterprise_data/utils.py
CHANGED
@@ -114,3 +114,22 @@ def find_first(iterable, condition):
|
|
114
114
|
return next(item for item in iterable if condition(item))
|
115
115
|
except StopIteration:
|
116
116
|
return None
|
117
|
+
|
118
|
+
|
119
|
+
def calculate_percentage_difference(first, second):
|
120
|
+
"""
|
121
|
+
Calculate the percentage difference between two numbers.
|
122
|
+
|
123
|
+
It will calculate the percentage difference between the two numbers using the formula:
|
124
|
+
((second - first) / (first + second)/2) * 100
|
125
|
+
|
126
|
+
Arguments:
|
127
|
+
first (float): The first number.
|
128
|
+
second (float): The second number.
|
129
|
+
|
130
|
+
Returns:
|
131
|
+
float: The percentage difference between the two numbers.
|
132
|
+
"""
|
133
|
+
if first == 0 and second == 0:
|
134
|
+
return 0
|
135
|
+
return ((second - first) / ((first + second) / 2)) * 100
|
File without changes
|
File without changes
|
File without changes
|