edx-enterprise-data 8.4.0__py3-none-any.whl → 8.6.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.
@@ -0,0 +1,375 @@
1
+ """Advance Analytics for Enrollments"""
2
+ from datetime import datetime, timedelta
3
+
4
+ from edx_rbac.decorators import permission_required
5
+ from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
6
+ from rest_framework.response import Response
7
+ from rest_framework.views import APIView
8
+
9
+ from django.http import HttpResponse, StreamingHttpResponse
10
+
11
+ from enterprise_data.admin_analytics.constants import CALCULATION, ENROLLMENT_CSV, GRANULARITY
12
+ from enterprise_data.admin_analytics.data_loaders import fetch_max_enrollment_datetime
13
+ from enterprise_data.admin_analytics.utils import (
14
+ calculation_aggregation,
15
+ fetch_and_cache_enrollments_data,
16
+ granularity_aggregation,
17
+ )
18
+ from enterprise_data.api.v1.paginators import AdvanceAnalyticsPagination
19
+ from enterprise_data.api.v1.serializers import (
20
+ AdvanceAnalyticsEnrollmentSerializer,
21
+ AdvanceAnalyticsEnrollmentStatsSerializer,
22
+ )
23
+ from enterprise_data.renderers import IndividualEnrollmentsCSVRenderer
24
+ from enterprise_data.utils import date_filter
25
+
26
+
27
+ def fetch_enrollments_cache_expiry_timestamp():
28
+ """Calculate cache expiry timestamp"""
29
+ # TODO: Implement correct cache expiry logic for `enrollments` data.
30
+ # Current cache expiry logic is based on `enterprise_learner_enrollment` table,
31
+ # Which has nothing to do with the `enrollments` data. Instead cache expiry should
32
+ # be based on `fact_enrollment_admin_dash` table. Currently we have no timestamp in
33
+ # `fact_enrollment_admin_dash` table that can be used for cache expiry. Add a new
34
+ # column in the table for this purpose and then use that column for cache expiry.
35
+ last_updated_at = fetch_max_enrollment_datetime()
36
+ cache_expiry = (
37
+ last_updated_at + timedelta(days=1) if last_updated_at else datetime.now()
38
+ )
39
+ return cache_expiry
40
+
41
+
42
+ class AdvanceAnalyticsIndividualEnrollmentsView(APIView):
43
+ """
44
+ API for getting the advance analytics individual enrollments data.
45
+ """
46
+ authentication_classes = (JwtAuthentication,)
47
+ pagination_class = AdvanceAnalyticsPagination
48
+ http_method_names = ['get']
49
+
50
+ @permission_required('can_access_enterprise', fn=lambda request, enterprise_uuid: enterprise_uuid)
51
+ def get(self, request, enterprise_uuid):
52
+ """Get individual enrollments data"""
53
+ serializer = AdvanceAnalyticsEnrollmentSerializer(data=request.GET)
54
+ serializer.is_valid(raise_exception=True)
55
+
56
+ cache_expiry = fetch_enrollments_cache_expiry_timestamp()
57
+ enrollments_df = fetch_and_cache_enrollments_data(enterprise_uuid, cache_expiry)
58
+
59
+ # get values from query params or use default values
60
+ start_date = serializer.data.get('start_date', enrollments_df.enterprise_enrollment_date.min())
61
+ end_date = serializer.data.get('end_date', datetime.now())
62
+ csv_type = request.query_params.get('csv_type')
63
+
64
+ # filter enrollments by date
65
+ enrollments = date_filter(start_date, end_date, enrollments_df, "enterprise_enrollment_date")
66
+
67
+ # select only the columns that will be in the table.
68
+ enrollments = enrollments[
69
+ [
70
+ "email",
71
+ "course_title",
72
+ "course_subject",
73
+ "enroll_type",
74
+ "enterprise_enrollment_date",
75
+ ]
76
+ ]
77
+ enrollments["enterprise_enrollment_date"] = enrollments["enterprise_enrollment_date"].dt.date
78
+ enrollments = enrollments.sort_values(by="enterprise_enrollment_date", ascending=False).reset_index(drop=True)
79
+
80
+ if csv_type == ENROLLMENT_CSV.INDIVIDUAL_ENROLLMENTS.value:
81
+ return StreamingHttpResponse(
82
+ IndividualEnrollmentsCSVRenderer().render(self._stream_serialized_data(enrollments)),
83
+ content_type="text/csv",
84
+ headers={"Content-Disposition": 'attachment; filename="individual_enrollments.csv"'},
85
+ )
86
+
87
+ paginator = self.pagination_class()
88
+ page = paginator.paginate_queryset(enrollments, request)
89
+ serialized_data = page.data.to_dict(orient='records')
90
+ response = paginator.get_paginated_response(serialized_data)
91
+
92
+ return response
93
+
94
+ def _stream_serialized_data(self, enrollments, chunk_size=50000):
95
+ """
96
+ Stream the serialized data.
97
+ """
98
+ total_rows = enrollments.shape[0]
99
+ for start_index in range(0, total_rows, chunk_size):
100
+ end_index = min(start_index + chunk_size, total_rows)
101
+ chunk = enrollments.iloc[start_index:end_index]
102
+ yield from chunk.to_dict(orient='records')
103
+
104
+
105
+ class AdvanceAnalyticsEnrollmentStatsView(APIView):
106
+ """
107
+ API for getting the advance analytics enrollment chart stats.
108
+ """
109
+ authentication_classes = (JwtAuthentication,)
110
+ http_method_names = ['get']
111
+
112
+ @permission_required('can_access_enterprise', fn=lambda request, enterprise_uuid: enterprise_uuid)
113
+ def get(self, request, enterprise_uuid): # lint-amnesty, pylint: disable=inconsistent-return-statements
114
+ """Get enrollment chart stats"""
115
+ serializer = AdvanceAnalyticsEnrollmentStatsSerializer(data=request.GET)
116
+ serializer.is_valid(raise_exception=True)
117
+
118
+ cache_expiry = fetch_enrollments_cache_expiry_timestamp()
119
+ enrollments_df = fetch_and_cache_enrollments_data(enterprise_uuid, cache_expiry)
120
+
121
+ # get values from query params or use default
122
+ start_date = serializer.data.get('start_date', enrollments_df.enterprise_enrollment_date.min())
123
+ end_date = serializer.data.get('end_date', datetime.now())
124
+ granularity = serializer.data.get('granularity', GRANULARITY.DAILY.value)
125
+ calculation = serializer.data.get('calculation', CALCULATION.TOTAL.value)
126
+ csv_type = serializer.data.get('csv_type')
127
+
128
+ if csv_type is None:
129
+ data = {
130
+ "enrollments_over_time": self.construct_enrollments_over_time(
131
+ enrollments_df.copy(),
132
+ start_date,
133
+ end_date,
134
+ granularity,
135
+ calculation,
136
+ ),
137
+ "top_courses_by_enrollments": self.construct_top_courses_by_enrollments(
138
+ enrollments_df.copy(),
139
+ start_date,
140
+ end_date,
141
+ ),
142
+ "top_subjects_by_enrollments": self.construct_top_subjects_by_enrollments(
143
+ enrollments_df.copy(),
144
+ start_date,
145
+ end_date,
146
+ ),
147
+ }
148
+ return Response(data)
149
+ elif csv_type == ENROLLMENT_CSV.ENROLLMENTS_OVER_TIME.value:
150
+ return self.construct_enrollments_over_time_csv(
151
+ enrollments_df.copy(),
152
+ start_date,
153
+ end_date,
154
+ granularity,
155
+ calculation,
156
+ )
157
+ elif csv_type == ENROLLMENT_CSV.TOP_COURSES_BY_ENROLLMENTS.value:
158
+ return self.construct_top_courses_by_enrollments_csv(
159
+ enrollments_df.copy(),
160
+ start_date,
161
+ end_date,
162
+ )
163
+ elif csv_type == ENROLLMENT_CSV.TOP_SUBJECTS_BY_ENROLLMENTS.value:
164
+ return self.construct_top_subjects_by_enrollments_csv(
165
+ enrollments_df.copy(),
166
+ start_date,
167
+ end_date,
168
+ )
169
+
170
+ def enrollments_over_time_common(self, enrollments_df, start_date, end_date, granularity, calculation):
171
+ """
172
+ Common method for constructing enrollments over time data.
173
+
174
+ Arguments:
175
+ enrollments_df {DataFrame} -- DataFrame of enrollments
176
+ start_date {datetime} -- Enrollment start date in the format 'YYYY-MM-DD'
177
+ end_date {datetime} -- Enrollment end date in the format 'YYYY-MM-DD'
178
+ granularity {str} -- Granularity of the data. One of GRANULARITY choices
179
+ calculation {str} -- Calculation of the data. One of CALCULATION choices
180
+ """
181
+ # filter enrollments by date
182
+ enrollments = date_filter(start_date, end_date, enrollments_df, "enterprise_enrollment_date")
183
+
184
+ # aggregate enrollments by granularity
185
+ enrollments = granularity_aggregation(
186
+ level=granularity,
187
+ group=["enterprise_enrollment_date", "enroll_type"],
188
+ date="enterprise_enrollment_date",
189
+ data_frame=enrollments,
190
+ )
191
+
192
+ # aggregate enrollments by calculation
193
+ enrollments = calculation_aggregation(calc=calculation, data_frame=enrollments)
194
+
195
+ return enrollments
196
+
197
+ def construct_enrollments_over_time(self, enrollments_df, start_date, end_date, granularity, calculation):
198
+ """
199
+ Construct enrollments over time data.
200
+
201
+ Arguments:
202
+ enrollments_df {DataFrame} -- DataFrame of enrollments
203
+ start_date {datetime} -- Enrollment start date in the format 'YYYY-MM-DD'
204
+ end_date {datetime} -- Enrollment end date in the format 'YYYY-MM-DD'
205
+ granularity {str} -- Granularity of the data. One of GRANULARITY choices
206
+ calculation {str} -- Calculation of the data. One of CALCULATION choices
207
+ """
208
+ enrollments = self.enrollments_over_time_common(enrollments_df, start_date, end_date, granularity, calculation)
209
+
210
+ # convert dataframe to a list of records
211
+ return enrollments.to_dict(orient='records')
212
+
213
+ def construct_enrollments_over_time_csv(self, enrollments_df, start_date, end_date, granularity, calculation):
214
+ """
215
+ Construct enrollments over time CSV.
216
+
217
+ Arguments:
218
+ enrollments_df {DataFrame} -- DataFrame of enrollments
219
+ start_date {datetime} -- Enrollment start date in the format 'YYYY-MM-DD'
220
+ end_date {datetime} -- Enrollment end date in the format 'YYYY-MM-DD'
221
+ granularity {str} -- Granularity of the data. One of GRANULARITY choices
222
+ calculation {str} -- Calculation of the data. One of CALCULATION choices
223
+ """
224
+ enrollments = self.enrollments_over_time_common(enrollments_df, start_date, end_date, granularity, calculation)
225
+
226
+ enrollments = enrollments.pivot(
227
+ index="enterprise_enrollment_date", columns="enroll_type", values="count"
228
+ )
229
+
230
+ filename = f"Enrollment Timeseries, {start_date} - {end_date} ({granularity} {calculation}).csv"
231
+ return self.construct_csv_response(enrollments, filename)
232
+
233
+ def top_courses_by_enrollments_common(self, enrollments_df, start_date, end_date, group_by_columns, columns):
234
+ """
235
+ Common method for constructing top courses by enrollments data.
236
+
237
+ Arguments:
238
+ enrollments_df {DataFrame} -- DataFrame of enrollments
239
+ start_date {datetime} -- Enrollment start date in the format 'YYYY-MM-DD'
240
+ end_date {datetime} -- Enrollment end date in the format 'YYYY-MM-DD'
241
+ group_by_columns {list} -- List of columns to group by
242
+ columns {list} -- List of column for the final result
243
+ """
244
+ # filter enrollments by date
245
+ enrollments = date_filter(start_date, end_date, enrollments_df, "enterprise_enrollment_date")
246
+
247
+ courses = list(
248
+ enrollments.groupby(["course_key"]).size().sort_values(ascending=False)[:10].index
249
+ )
250
+
251
+ enrollments = (
252
+ enrollments[enrollments.course_key.isin(courses)]
253
+ .groupby(group_by_columns)
254
+ .size()
255
+ .reset_index()
256
+ )
257
+ enrollments.columns = columns
258
+
259
+ return enrollments
260
+
261
+ def construct_top_courses_by_enrollments(self, enrollments_df, start_date, end_date):
262
+ """
263
+ Construct top courses by enrollments data.
264
+
265
+ Arguments:
266
+ enrollments_df {DataFrame} -- DataFrame of enrollments
267
+ start_date {datetime} -- Enrollment start date in the format 'YYYY-MM-DD'
268
+ end_date {datetime} -- Enrollment end date in the format 'YYYY-MM-DD'
269
+ """
270
+ group_by_columns = ["course_key", "enroll_type"]
271
+ columns = ["course_key", "enroll_type", "count"]
272
+ enrollments = self.top_courses_by_enrollments_common(
273
+ enrollments_df,
274
+ start_date,
275
+ end_date,
276
+ group_by_columns,
277
+ columns
278
+ )
279
+
280
+ # convert dataframe to a list of records
281
+ return enrollments.to_dict(orient='records')
282
+
283
+ def construct_top_courses_by_enrollments_csv(self, enrollments_df, start_date, end_date):
284
+ """
285
+ Construct top courses by enrollments CSV.
286
+
287
+ Arguments:
288
+ enrollments_df {DataFrame} -- DataFrame of enrollments
289
+ start_date {datetime} -- Enrollment start date in the format 'YYYY-MM-DD'
290
+ end_date {datetime} -- Enrollment end date in the format 'YYYY-MM-DD'
291
+ """
292
+ group_by_columns = ["course_key", "course_title", "enroll_type"]
293
+ columns = ["course_key", "course_title", "enroll_type", "count"]
294
+ enrollments = self.top_courses_by_enrollments_common(
295
+ enrollments_df,
296
+ start_date,
297
+ end_date,
298
+ group_by_columns,
299
+ columns
300
+ )
301
+
302
+ enrollments = enrollments.pivot(
303
+ index=["course_key", "course_title"], columns="enroll_type", values="count"
304
+ )
305
+
306
+ filename = f"Top 10 Courses, {start_date} - {end_date}.csv"
307
+ return self.construct_csv_response(enrollments, filename)
308
+
309
+ def top_subjects_by_enrollments_common(self, enrollments_df, start_date, end_date):
310
+ """
311
+ Common method for constructing top subjects by enrollments data.
312
+
313
+ Arguments:
314
+ enrollments_df {DataFrame} -- DataFrame of enrollments
315
+ start_date {datetime} -- Enrollment start date in the format 'YYYY-MM-DD'
316
+ end_date {datetime} -- Enrollment end date in the format 'YYYY-MM-DD'
317
+ """
318
+ # filter enrollments by date
319
+ enrollments = date_filter(start_date, end_date, enrollments_df, "enterprise_enrollment_date")
320
+
321
+ subjects = list(
322
+ enrollments.groupby(["course_subject"]).size().sort_values(ascending=False)[:10].index
323
+ )
324
+
325
+ enrollments = (
326
+ enrollments[enrollments.course_subject.isin(subjects)]
327
+ .groupby(["course_subject", "enroll_type"])
328
+ .size()
329
+ .reset_index()
330
+ )
331
+ enrollments.columns = ["course_subject", "enroll_type", "count"]
332
+
333
+ return enrollments
334
+
335
+ def construct_top_subjects_by_enrollments(self, enrollments_df, start_date, end_date):
336
+ """
337
+ Construct top subjects by enrollments data.
338
+
339
+ Arguments:
340
+ enrollments_df {DataFrame} -- DataFrame of enrollments
341
+ start_date {datetime} -- Enrollment start date in the format 'YYYY-MM-DD'
342
+ end_date {datetime} -- Enrollment end date in the format 'YYYY-MM-DD'
343
+ """
344
+ enrollments = self.top_subjects_by_enrollments_common(enrollments_df, start_date, end_date)
345
+ # convert dataframe to a list of records
346
+ return enrollments.to_dict(orient='records')
347
+
348
+ def construct_top_subjects_by_enrollments_csv(self, enrollments_df, start_date, end_date):
349
+ """
350
+ Construct top subjects by enrollments CSV.
351
+
352
+ Arguments:
353
+ enrollments_df {DataFrame} -- DataFrame of enrollments
354
+ start_date {datetime} -- Enrollment start date in the format 'YYYY-MM-DD'
355
+ end_date {datetime} -- Enrollment end date in the format 'YYYY-MM-DD'
356
+ """
357
+ enrollments = self.top_subjects_by_enrollments_common(enrollments_df, start_date, end_date)
358
+ enrollments = enrollments.pivot(index="course_subject", columns="enroll_type", values="count")
359
+
360
+ filename = f"Top 10 Subjects by Enrollment, {start_date} - {end_date}.csv"
361
+ return self.construct_csv_response(enrollments, filename)
362
+
363
+ def construct_csv_response(self, enrollments, filename):
364
+ """
365
+ Construct CSV response.
366
+
367
+ Arguments:
368
+ enrollments {DataFrame} -- DataFrame of enrollments
369
+ filename {str} -- Filename for the CSV
370
+ """
371
+ response = HttpResponse(content_type='text/csv')
372
+ response['Content-Disposition'] = f'attachment; filename="{filename}"'
373
+ enrollments.to_csv(path_or_buf=response)
374
+
375
+ return response
@@ -1,11 +1,11 @@
1
1
  """
2
2
  Views for enterprise admin api v1.
3
3
  """
4
-
5
4
  from datetime import datetime, timedelta
6
5
 
7
6
  from edx_rbac.decorators import permission_required
8
7
  from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
8
+ from rest_framework import filters, viewsets
9
9
  from rest_framework.response import Response
10
10
  from rest_framework.status import HTTP_200_OK, HTTP_404_NOT_FOUND
11
11
  from rest_framework.views import APIView
@@ -22,9 +22,15 @@ from enterprise_data.admin_analytics.utils import (
22
22
  get_top_skills_csv_data,
23
23
  )
24
24
  from enterprise_data.api.v1 import serializers
25
- from enterprise_data.models import EnterpriseAdminLearnerProgress, EnterpriseAdminSummarizeInsights
25
+ from enterprise_data.models import (
26
+ EnterpriseAdminLearnerProgress,
27
+ EnterpriseAdminSummarizeInsights,
28
+ EnterpriseExecEdLCModulePerformance,
29
+ )
26
30
  from enterprise_data.utils import date_filter
27
31
 
32
+ from .base import EnterpriseViewSetMixin
33
+
28
34
 
29
35
  class EnterpriseAdminInsightsView(APIView):
30
36
  """
@@ -109,16 +115,16 @@ class EnterpriseAdminAnalyticsAggregatesView(APIView):
109
115
  # 1. minimum enrollment date from the data as the start_date
110
116
  # 2. today's date as the end_date
111
117
  start_date = serializer.data.get(
112
- "start_date", enrollment.enterprise_enrollment_date.min()
118
+ 'start_date', enrollment.enterprise_enrollment_date.min()
113
119
  )
114
- end_date = serializer.data.get("end_date", datetime.now())
120
+ end_date = serializer.data.get('end_date', datetime.now())
115
121
 
116
122
  # Date filtering.
117
123
  dff = date_filter(
118
124
  start=start_date,
119
125
  end=end_date,
120
126
  data_frame=enrollment.copy(),
121
- date_column="enterprise_enrollment_date",
127
+ date_column='enterprise_enrollment_date',
122
128
  )
123
129
 
124
130
  enrolls = len(dff)
@@ -128,7 +134,7 @@ class EnterpriseAdminAnalyticsAggregatesView(APIView):
128
134
  start=start_date,
129
135
  end=end_date,
130
136
  data_frame=enrollment.copy(),
131
- date_column="passed_date",
137
+ date_column='passed_date',
132
138
  )
133
139
 
134
140
  completions = dff.has_passed.sum()
@@ -138,7 +144,7 @@ class EnterpriseAdminAnalyticsAggregatesView(APIView):
138
144
  start=start_date,
139
145
  end=end_date,
140
146
  data_frame=engagement.copy(),
141
- date_column="activity_date",
147
+ date_column='activity_date',
142
148
  )
143
149
 
144
150
  hours = round(dff.learning_time_seconds.sum() / 60 / 60, 1)
@@ -146,14 +152,14 @@ class EnterpriseAdminAnalyticsAggregatesView(APIView):
146
152
 
147
153
  return Response(
148
154
  data={
149
- "enrolls": enrolls,
150
- "courses": courses,
151
- "completions": completions,
152
- "hours": hours,
153
- "sessions": sessions,
154
- "last_updated_at": last_updated_at.date() if last_updated_at else None,
155
- "min_enrollment_date": enrollment.enterprise_enrollment_date.min().date(),
156
- "max_enrollment_date": enrollment.enterprise_enrollment_date.max().date(),
155
+ 'enrolls': enrolls,
156
+ 'courses': courses,
157
+ 'completions': completions,
158
+ 'hours': hours,
159
+ 'sessions': sessions,
160
+ 'last_updated_at': last_updated_at.date() if last_updated_at else None,
161
+ 'min_enrollment_date': enrollment.enterprise_enrollment_date.min().date(),
162
+ 'max_enrollment_date': enrollment.enterprise_enrollment_date.max().date(),
157
163
  },
158
164
  status=HTTP_200_OK,
159
165
  )
@@ -231,3 +237,25 @@ class EnterpriseAdminAnalyticsSkillsView(APIView):
231
237
  }
232
238
 
233
239
  return Response(data=response_data, status=HTTP_200_OK)
240
+
241
+
242
+ class EnterpriseExecEdLCModulePerformanceViewSet(EnterpriseViewSetMixin, viewsets.ReadOnlyModelViewSet):
243
+ """
244
+ View to for getting enterprise exec ed learner module performance records.
245
+ """
246
+ serializer_class = serializers.EnterpriseExecEdLCModulePerformanceSerializer
247
+ filter_backends = (filters.OrderingFilter, filters.SearchFilter)
248
+ ordering_fields = '__all__'
249
+ ordering = ('last_access',)
250
+ search_fields = (
251
+ 'username',
252
+ 'course_name'
253
+ )
254
+
255
+ def get_queryset(self):
256
+ """
257
+ Return the queryset of EnterpriseExecEdLCModulePerformance objects.
258
+ """
259
+ return EnterpriseExecEdLCModulePerformance.objects.filter(
260
+ enterprise_customer_uuid=self.kwargs['enterprise_id'],
261
+ )
@@ -0,0 +1,87 @@
1
+ # Generated by Django 4.2.14 on 2024-08-08 07:43
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('enterprise_data', '0040_auto_20240718_0536_squashed_0043_alter_enterpriselearnerenrollment_enterprise_enrollment_id'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.CreateModel(
14
+ name='EnterpriseExecEdLCModulePerformance',
15
+ fields=[
16
+ ('module_performance_unique_id', models.CharField(max_length=350, primary_key=True, serialize=False)),
17
+ ('registration_id', models.PositiveIntegerField(null=True)),
18
+ ('subsidy_transaction_id', models.UUIDField(null=True)),
19
+ ('enterprise_customer_uuid', models.UUIDField(db_index=True, null=True)),
20
+ ('ocm_lms_user_id', models.PositiveIntegerField(null=True)),
21
+ ('is_internal_subsidy', models.BooleanField(null=True)),
22
+ ('ocm_enrollment_id', models.PositiveIntegerField(null=True)),
23
+ ('ocm_courserun_key', models.CharField(max_length=255, null=True)),
24
+ ('university_name', models.CharField(max_length=500, null=True)),
25
+ ('university_abbreviation', models.CharField(max_length=255, null=True)),
26
+ ('partner_short_name', models.CharField(max_length=255, null=True)),
27
+ ('university_country', models.CharField(max_length=255, null=True)),
28
+ ('school', models.CharField(max_length=500, null=True)),
29
+ ('faculty', models.CharField(max_length=500, null=True)),
30
+ ('department', models.CharField(max_length=500, null=True)),
31
+ ('course_code', models.PositiveIntegerField(null=True)),
32
+ ('course_name', models.CharField(db_index=True, max_length=500)),
33
+ ('course_abbreviation', models.CharField(max_length=128, null=True)),
34
+ ('course_abbreviation_short', models.CharField(max_length=64, null=True)),
35
+ ('course_type', models.CharField(max_length=128, null=True)),
36
+ ('subject_vertical', models.CharField(max_length=128, null=True)),
37
+ ('presentation_abbreviation', models.CharField(max_length=128, null=True)),
38
+ ('presentation_name', models.CharField(max_length=500, null=True)),
39
+ ('presentation_code', models.PositiveIntegerField(null=True)),
40
+ ('presentation_close_date', models.DateField(null=True)),
41
+ ('presentation_start_date', models.DateField(null=True)),
42
+ ('promotion_code', models.CharField(max_length=255, null=True)),
43
+ ('promotion_category_name', models.CharField(max_length=255, null=True)),
44
+ ('product_type', models.CharField(max_length=128, null=True)),
45
+ ('product_life_cycle_status', models.CharField(max_length=128, null=True)),
46
+ ('enrolment_id', models.PositiveIntegerField(null=True)),
47
+ ('olc_user_id', models.PositiveIntegerField(null=True)),
48
+ ('first_name', models.CharField(max_length=255, null=True)),
49
+ ('last_name', models.CharField(max_length=255, null=True)),
50
+ ('username', models.CharField(db_index=True, max_length=255)),
51
+ ('status', models.CharField(max_length=128, null=True)),
52
+ ('company_name', models.CharField(max_length=255, null=True)),
53
+ ('module_number', models.PositiveIntegerField(null=True)),
54
+ ('module_name', models.CharField(max_length=255, null=True)),
55
+ ('module_1_release_date', models.DateField(null=True)),
56
+ ('last_module_release_date', models.DateField(null=True)),
57
+ ('last_module_end_date', models.DateField(null=True)),
58
+ ('final_mark', models.DecimalField(decimal_places=2, max_digits=38, null=True)),
59
+ ('assign_grade', models.DecimalField(decimal_places=2, max_digits=38, null=True)),
60
+ ('last_access', models.DateField(null=True)),
61
+ ('all_activities_completed_count', models.PositiveIntegerField(null=True)),
62
+ ('all_activities_total_count', models.PositiveIntegerField(null=True)),
63
+ ('percentage_completed_activities', models.DecimalField(decimal_places=2, max_digits=38, null=True)),
64
+ ('extensions_requested', models.PositiveIntegerField(null=True)),
65
+ ('module_grade', models.DecimalField(decimal_places=2, max_digits=38, null=True)),
66
+ ('log_viewed', models.PositiveIntegerField(null=True)),
67
+ ('hours_online', models.DecimalField(decimal_places=2, max_digits=38, null=True)),
68
+ ('orientation_module_accessed', models.CharField(max_length=128, null=True)),
69
+ ('graded_activities_completed_count', models.PositiveIntegerField(null=True)),
70
+ ('graded_activities_total_count', models.PositiveIntegerField(null=True)),
71
+ ('percentage_completed_graded_activities', models.DecimalField(decimal_places=2, max_digits=38, null=True)),
72
+ ('assessment_activities_completed_count', models.PositiveIntegerField(null=True)),
73
+ ('assessment_activities_total_count', models.PositiveIntegerField(null=True)),
74
+ ('course_material_activities_completed_count', models.PositiveIntegerField(null=True)),
75
+ ('course_material_activities_total_count', models.PositiveIntegerField(null=True)),
76
+ ('discussion_forum_activities_completed_count', models.PositiveIntegerField(null=True)),
77
+ ('discussion_forum_activities_total_count', models.PositiveIntegerField(null=True)),
78
+ ('pass_grade', models.DecimalField(decimal_places=2, max_digits=38, null=True)),
79
+ ],
80
+ options={
81
+ 'verbose_name': 'Exec Ed LC Module Performance',
82
+ 'verbose_name_plural': 'Exec Ed LC Module Performance',
83
+ 'db_table': 'exec_ed_lc_module_performance',
84
+ 'ordering': ['last_access'],
85
+ },
86
+ ),
87
+ ]
enterprise_data/models.py CHANGED
@@ -434,3 +434,97 @@ class EnterpriseUser(models.Model):
434
434
  Return uniquely identifying string representation.
435
435
  """
436
436
  return self.__str__()
437
+
438
+
439
+ class EnterpriseExecEdLCModulePerformance(models.Model):
440
+ """
441
+ Model for Exec Ed LC Module Performance.
442
+ """
443
+
444
+ objects = EnterpriseReportingModelManager()
445
+
446
+ class Meta:
447
+ app_label = 'enterprise_data'
448
+ db_table = 'exec_ed_lc_module_performance'
449
+ verbose_name = _("Exec Ed LC Module Performance")
450
+ verbose_name_plural = _("Exec Ed LC Module Performance")
451
+
452
+ module_performance_unique_id = models.CharField(max_length=350, primary_key=True)
453
+ registration_id = models.PositiveIntegerField(null=True)
454
+ subsidy_transaction_id = models.UUIDField(null=True)
455
+ enterprise_customer_uuid = models.UUIDField(db_index=True, null=True)
456
+ ocm_lms_user_id = models.PositiveIntegerField(null=True)
457
+ is_internal_subsidy = models.BooleanField(null=True)
458
+ ocm_enrollment_id = models.PositiveIntegerField(null=True)
459
+ ocm_courserun_key = models.CharField(max_length=255, null=True)
460
+ university_name = models.CharField(max_length=500, null=True)
461
+ university_abbreviation = models.CharField(max_length=255, null=True)
462
+ partner_short_name = models.CharField(max_length=255, null=True)
463
+ university_country = models.CharField(max_length=255, null=True)
464
+ school = models.CharField(max_length=500, null=True)
465
+ faculty = models.CharField(max_length=500, null=True)
466
+ department = models.CharField(max_length=500, null=True)
467
+ course_code = models.PositiveIntegerField(null=True)
468
+ course_name = models.CharField(max_length=500, db_index=True)
469
+ course_abbreviation = models.CharField(max_length=128, null=True)
470
+ course_abbreviation_short = models.CharField(max_length=64, null=True)
471
+ course_type = models.CharField(max_length=128, null=True)
472
+ subject_vertical = models.CharField(max_length=128, null=True)
473
+ presentation_abbreviation = models.CharField(max_length=128, null=True)
474
+ presentation_name = models.CharField(max_length=500, null=True)
475
+ presentation_code = models.PositiveIntegerField(null=True)
476
+ presentation_close_date = models.DateField(null=True)
477
+ presentation_start_date = models.DateField(null=True)
478
+ promotion_code = models.CharField(max_length=255, null=True)
479
+ promotion_category_name = models.CharField(max_length=255, null=True)
480
+ product_type = models.CharField(max_length=128, null=True)
481
+ product_life_cycle_status = models.CharField(max_length=128, null=True)
482
+ enrolment_id = models.PositiveIntegerField(null=True)
483
+ olc_user_id = models.PositiveIntegerField(null=True)
484
+ first_name = models.CharField(max_length=255, null=True)
485
+ last_name = models.CharField(max_length=255, null=True)
486
+ username = models.CharField(max_length=255, db_index=True)
487
+ status = models.CharField(max_length=128, null=True)
488
+ company_name = models.CharField(max_length=255, null=True)
489
+ module_number = models.PositiveIntegerField(null=True)
490
+ module_name = models.CharField(max_length=255, null=True)
491
+ module_1_release_date = models.DateField(null=True)
492
+ last_module_release_date = models.DateField(null=True)
493
+ last_module_end_date = models.DateField(null=True)
494
+ final_mark = models.DecimalField(max_digits=38, decimal_places=2, null=True)
495
+ assign_grade = models.DecimalField(max_digits=38, decimal_places=2, null=True)
496
+ last_access = models.DateField(null=True)
497
+ all_activities_completed_count = models.PositiveIntegerField(null=True)
498
+ all_activities_total_count = models.PositiveIntegerField(null=True)
499
+ percentage_completed_activities = models.DecimalField(max_digits=38, decimal_places=2, null=True)
500
+ extensions_requested = models.PositiveIntegerField(null=True)
501
+ module_grade = models.DecimalField(max_digits=38, decimal_places=2, null=True)
502
+ log_viewed = models.PositiveIntegerField(null=True)
503
+ hours_online = models.DecimalField(max_digits=38, decimal_places=2, null=True)
504
+ orientation_module_accessed = models.CharField(max_length=128, null=True)
505
+ graded_activities_completed_count = models.PositiveIntegerField(null=True)
506
+ graded_activities_total_count = models.PositiveIntegerField(null=True)
507
+ percentage_completed_graded_activities = models.DecimalField(max_digits=38, decimal_places=2, null=True)
508
+ assessment_activities_completed_count = models.PositiveIntegerField(null=True)
509
+ assessment_activities_total_count = models.PositiveIntegerField(null=True)
510
+ course_material_activities_completed_count = models.PositiveIntegerField(null=True)
511
+ course_material_activities_total_count = models.PositiveIntegerField(null=True)
512
+ discussion_forum_activities_completed_count = models.PositiveIntegerField(null=True)
513
+ discussion_forum_activities_total_count = models.PositiveIntegerField(null=True)
514
+ pass_grade = models.DecimalField(max_digits=38, decimal_places=2, null=True)
515
+
516
+ def __str__(self):
517
+ """
518
+ Return a human-readable string representation of the object.
519
+ """
520
+ return "<EnterpriseExecEdLCModulePerformance User {user} of {enterprise} in module {module}>".format(
521
+ user=self.ocm_lms_user_id,
522
+ enterprise=self.enterprise_customer_uuid,
523
+ module=self.module_number,
524
+ )
525
+
526
+ def __repr__(self):
527
+ """
528
+ Return uniquely identifying string representation.
529
+ """
530
+ return self.__str__()