edx-enterprise-data 10.6.1__py3-none-any.whl → 10.7.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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: edx-enterprise-data
3
- Version: 10.6.1
3
+ Version: 10.7.1
4
4
  Summary: Enterprise Reporting
5
5
  Home-page: https://github.com/openedx/edx-enterprise-data
6
6
  Author: edX
@@ -39,5 +39,14 @@ Requires-Dist: pyminizip; extra == "reporting"
39
39
  Requires-Dist: snowflake-connector-python; extra == "reporting"
40
40
  Requires-Dist: unicodecsv==0.14.1; extra == "reporting"
41
41
  Requires-Dist: vertica-python; extra == "reporting"
42
+ Dynamic: author
43
+ Dynamic: author-email
44
+ Dynamic: classifier
45
+ Dynamic: description
46
+ Dynamic: home-page
47
+ Dynamic: license
48
+ Dynamic: provides-extra
49
+ Dynamic: requires-dist
50
+ Dynamic: summary
42
51
 
43
52
  Tools and products related to providing access to Enterprise data.
@@ -1,11 +1,11 @@
1
- enterprise_data/__init__.py,sha256=08lJCu5sydEWhQx7P2U6ZGQRtK9tamTRcgjDmW05Bf0,124
1
+ enterprise_data/__init__.py,sha256=T3-xDyVCoeDZ4bgWeuzzKbgUMb5zRaAjz6DbHxuI_3w,124
2
2
  enterprise_data/apps.py,sha256=aF6hZwDfI2oWj95tUTm_2ikHueQj-jLj-u0GrgzpsQI,414
3
3
  enterprise_data/clients.py,sha256=GvQupy5TVYfO_IKC3yzXSAgNP54r-PtIjidM5ws9Iks,3947
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=khGcOh7NWP8KGu84t78Y2zAu3knREeXA_prApmU2NX8,24428
6
+ enterprise_data/models.py,sha256=INIETN4B6G1EtxVzgBQr6-JMy3ZsHj9K_FCyrS5OFAk,26312
7
7
  enterprise_data/paginators.py,sha256=YPrC5TeXFt-ymenT2H8H2nCbDCnAzJQlH9kFPElRxWE,269
8
- enterprise_data/renderers.py,sha256=d_bJZjeUTyHRBBtpCcslrTyldv6IMYQ_QW-GWijwGHU,3026
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
11
  enterprise_data/utils.py,sha256=sDrpBd62DpybCV41QCxRUaCuvch3qKjEhfUp9cA_GV0,2952
@@ -30,16 +30,16 @@ 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=oS09fMJyb3DkSc2y5TH5Yknd9NjYrlmWMRPNqBZ741U,10902
34
- enterprise_data/api/v1/urls.py,sha256=IpOyS9UWuyip6fw6gtrxBw9SZLOdxh3sQ2j0Gdk4eOw,4180
33
+ enterprise_data/api/v1/serializers.py,sha256=mya_ZvP7CTvUTF_ccYkMJZFuzOKIU-aM3eSCQmFazWY,11274
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
37
37
  enterprise_data/api/v1/views/analytics_engagements.py,sha256=Yo-bpA-0xOQHUPTFF0jHWxjm6KpSC-l2nGxhYX0ajBk,6298
38
38
  enterprise_data/api/v1/views/analytics_enrollments.py,sha256=hw87VZ0hFWpwf3QEHFn9cUgDy2s7SzsXt6Rf9TaNsS0,6282
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
- enterprise_data/api/v1/views/enterprise_admin.py,sha256=DyN6RS4qY8sgKNIvcx1nRmSGuPm2pLYHZeh5YHXlH2w,8209
42
- enterprise_data/api/v1/views/enterprise_learner.py,sha256=NqI_Tlz5v3p4fYZe2RRCg54AizslZdHB3Ckh8YQrhIM,18163
41
+ enterprise_data/api/v1/views/enterprise_admin.py,sha256=pb63EG_fpCRvegy7cw9HNeSuzumh3nJVe6KSPcBzsXw,8969
42
+ enterprise_data/api/v1/views/enterprise_learner.py,sha256=lCB2guZeIDf-CbcdDzT2K5K-OW1Vt5i4R8K9aT1ZzuM,18627
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
@@ -58,7 +58,7 @@ enterprise_data/management/commands/pre_warm_analytics_cache.py,sha256=3MCThZiZg
58
58
  enterprise_data/management/commands/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
59
  enterprise_data/management/commands/tests/test_create_dummy_data_lpr_v1.py,sha256=wt9fqAFKQVTqllpZ42dch-n31JavUifUIB9CKNYcnYM,1086
60
60
  enterprise_data/management/commands/tests/test_create_enterprise_enrollment.py,sha256=5CABLk8qAx8RP8mrFtnbJ4-xVkf9-5Mq6iQcx8jBfFc,1742
61
- enterprise_data/management/commands/tests/test_create_enterprise_learner_enrollment_lpr_v1.py,sha256=FzOBHcd1FfSVN5AI8oAKvmqj-W8Y-V7x9yJx9R1WrLQ,3845
61
+ enterprise_data/management/commands/tests/test_create_enterprise_learner_enrollment_lpr_v1.py,sha256=cw8PTUhwkFZeI3agCRLRMxKBRO6k1Ukft2CJIRUUMoY,4139
62
62
  enterprise_data/management/commands/tests/test_create_enterprise_learner_lpr_v1.py,sha256=P7wyyaZ4LMWv1umpCThp4By5DWjtI7kYAmY0AeuY2XI,1637
63
63
  enterprise_data/management/commands/tests/test_create_enterprise_user.py,sha256=0uxG332-jYpaxUIrMtIVRTwvRfxTWEQBIwZlIQO7f2g,1469
64
64
  enterprise_data/management/commands/tests/test_pre_warm_analytics_cache.py,sha256=Nbwsl5CYMbi5cN2IwmUtTs5b_MeU6T0lquk3x8Tuh_4,2108
@@ -104,6 +104,8 @@ enterprise_data/migrations/0038_enterpriseoffer_export_timestamp.py,sha256=8St3D
104
104
  enterprise_data/migrations/0039_auto_20240212_1403.py,sha256=rMiJcYx26ZGqPdQzgiWkUgbcRhD-tp9BgXdgt67vC2Q,1002
105
105
  enterprise_data/migrations/0040_auto_20240718_0536_squashed_0043_alter_enterpriselearnerenrollment_enterprise_enrollment_id.py,sha256=Kq_ResqoAsJjtyIysHjs4VaDxNh6p4fPMMyh93GIJxA,1693
106
106
  enterprise_data/migrations/0044_enterpriseexecedlcmoduleperformance.py,sha256=1aJ0wIu9K2cT3AJdBIHuNCUpJysPWmq_37Ucp5ntEO8,6122
107
+ enterprise_data/migrations/0045_alter_enterpriseexecedlcmoduleperformance_options_and_more.py,sha256=HJkY2OvuE39ciWgaEescd--ixQhTVrwzbL0_bvNofSo,876
108
+ enterprise_data/migrations/0046_enterprisegroupmembership.py,sha256=38qfi3EySDLrpP3gDuO_oUl_7rphGPZIrPsCuBhQ_dk,1766
107
109
  enterprise_data/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
108
110
  enterprise_data/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
109
111
  enterprise_data/settings/test.py,sha256=4-flfrlf81AthGx9wTaT5PscyoOWyhsDDqbzBl-z7Eg,4191
@@ -112,8 +114,8 @@ enterprise_data/tests/factories.py,sha256=EqiB8xDVN9n6ZrFV0d4h6hEiRF57dpbmOjHezU
112
114
  enterprise_data/tests/mixins.py,sha256=YifptI9mtOhAWnBGyPUy4kX5OJNSDP3DvW2vb1E2tvw,805
113
115
  enterprise_data/tests/test_clients.py,sha256=xBPHF9cgEFqNJoL4klOoYh_sVS3scZGcX0Ltc9Ghp7A,6336
114
116
  enterprise_data/tests/test_filters.py,sha256=ZBbLl9Sgj5mJ7lTWoaFcEPwuxPDpIbMo2n_Fhurc0T8,7263
115
- enterprise_data/tests/test_models.py,sha256=MWBY-LY5TPBjZ4GlvpM-h4W-BvRKr2Rml8Bzg1NPZ9M,3234
116
- enterprise_data/tests/test_utils.py,sha256=vbmYM7DMN-lHS2p4yaa0Yd6uSGXd2qoZRDE9X3J4Sec,18385
117
+ enterprise_data/tests/test_models.py,sha256=rRFrzjMnnUh1YpVvQMyA1OhFmX6BOCRt28SNAiPq6Ac,4179
118
+ enterprise_data/tests/test_utils.py,sha256=3sM3YWEoihHupgwhh0NiI66CbcQPLY4I_W0xjbLG0zg,19694
117
119
  enterprise_data/tests/test_views.py,sha256=UvDRNTxruy5zBK_KgUy2cBMbwlaTW_vkM0-TCXbQZiY,69667
118
120
  enterprise_data/tests/admin_analytics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
119
121
  enterprise_data/tests/admin_analytics/mock_analytics_data.py,sha256=qN73YxoD1kAtKsQo94avR0DGMCsJClrpqFxxYC6reqE,15248
@@ -171,8 +173,8 @@ enterprise_reporting/tests/test_send_enterprise_reports.py,sha256=WtL-RqGgu2x5PP
171
173
  enterprise_reporting/tests/test_utils.py,sha256=Zt_TA0LVb-B6fQGkUkAKKVlUKKnQh8jnw1US1jKe7g8,9493
172
174
  enterprise_reporting/tests/test_vertica_client.py,sha256=-R2yNCGUjRtoXwLMBloVFQkFYrJoo613VCr61gwI3kQ,140
173
175
  enterprise_reporting/tests/utils.py,sha256=xms2LM7DV3wczXEfctOK1ddel1EE0J_YSr17UzbCDy4,1401
174
- edx_enterprise_data-10.6.1.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
175
- edx_enterprise_data-10.6.1.dist-info/METADATA,sha256=wVdcfJuojxt3IGCjK-rgBOVcT6r6qQo4kFfje2aLRLo,1505
176
- edx_enterprise_data-10.6.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
177
- edx_enterprise_data-10.6.1.dist-info/top_level.txt,sha256=f5F2kU-dob6MqiHJpgZkFzoCD5VMhsdpkTV5n9Tvq3I,59
178
- edx_enterprise_data-10.6.1.dist-info/RECORD,,
176
+ edx_enterprise_data-10.7.1.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
177
+ edx_enterprise_data-10.7.1.dist-info/METADATA,sha256=5mDdjLLwwUOnOqLCyftUw3o9Eqwx91kwN4U5zBrRwIE,1684
178
+ edx_enterprise_data-10.7.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
179
+ edx_enterprise_data-10.7.1.dist-info/top_level.txt,sha256=f5F2kU-dob6MqiHJpgZkFzoCD5VMhsdpkTV5n9Tvq3I,59
180
+ edx_enterprise_data-10.7.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -2,4 +2,4 @@
2
2
  Enterprise data api application. This Django app exposes API endpoints used by enterprises.
3
3
  """
4
4
 
5
- __version__ = "10.6.1"
5
+ __version__ = "10.7.1"
@@ -10,6 +10,7 @@ from enterprise_data.models import (
10
10
  EnterpriseAdminLearnerProgress,
11
11
  EnterpriseAdminSummarizeInsights,
12
12
  EnterpriseExecEdLCModulePerformance,
13
+ EnterpriseGroupMembership,
13
14
  EnterpriseLearner,
14
15
  EnterpriseLearnerEnrollment,
15
16
  EnterpriseOffer,
@@ -41,9 +42,9 @@ class EnterpriseLearnerEnrollmentSerializer(serializers.ModelSerializer):
41
42
  'course_primary_program', 'primary_program_type', 'course_primary_subject', 'has_passed',
42
43
  'last_activity_date', 'progress_status', 'passed_date', 'current_grade',
43
44
  'letter_grade', 'enterprise_user_id', 'user_email', 'user_account_creation_date',
44
- 'user_country_code', 'user_username', 'enterprise_name', 'enterprise_customer_uuid',
45
- 'enterprise_sso_uid', 'created', 'course_api_url', 'total_learning_time_hours', 'is_subsidy',
46
- 'course_product_line', 'budget_id', 'enterprise_group_name', 'enterprise_group_uuid',
45
+ 'user_country_code', 'user_username', 'user_first_name', 'user_last_name', 'enterprise_name',
46
+ 'enterprise_customer_uuid', 'enterprise_sso_uid', 'created', 'course_api_url', 'total_learning_time_hours',
47
+ 'is_subsidy', 'course_product_line', 'budget_id', 'enterprise_group_name', 'enterprise_group_uuid',
47
48
  )
48
49
 
49
50
  def get_course_api_url(self, obj):
@@ -253,6 +254,19 @@ class EnterpriseBudgetSerializer(serializers.ModelSerializer):
253
254
  )
254
255
 
255
256
 
257
+ class EnterpriseGroupMembershipSerializer(serializers.ModelSerializer):
258
+ """
259
+ Serializer for EnterpriseGroupMembership model.
260
+ """
261
+
262
+ class Meta:
263
+ model = EnterpriseGroupMembership
264
+ fields = (
265
+ 'enterprise_group_uuid',
266
+ 'enterprise_group_name',
267
+ )
268
+
269
+
256
270
  class AdvanceAnalyticsQueryParamSerializer(serializers.Serializer): # pylint: disable=abstract-method
257
271
  """Serializer for validating query params"""
258
272
  RESPONSE_TYPES = [
@@ -101,6 +101,11 @@ urlpatterns = [
101
101
  enterprise_admin_views.EnterpriseBudgetView.as_view(),
102
102
  name='enterprise-budgets'
103
103
  ),
104
+ re_path(
105
+ fr'^enterprise/(?P<enterprise_uuid>{UUID4_REGEX})/groups',
106
+ enterprise_admin_views.EnterpriseGroupMembershipView.as_view(),
107
+ name='enterprise-groups'
108
+ ),
104
109
  ]
105
110
 
106
111
  urlpatterns += router.urls
@@ -21,6 +21,7 @@ from enterprise_data.models import (
21
21
  EnterpriseAdminLearnerProgress,
22
22
  EnterpriseAdminSummarizeInsights,
23
23
  EnterpriseExecEdLCModulePerformance,
24
+ EnterpriseGroupMembership,
24
25
  EnterpriseSubsidyBudget,
25
26
  )
26
27
  from enterprise_data.utils import timer
@@ -230,3 +231,24 @@ class EnterpriseBudgetView(APIView):
230
231
 
231
232
  serializer = serializers.EnterpriseBudgetSerializer(budgets, many=True)
232
233
  return Response(serializer.data)
234
+
235
+
236
+ class EnterpriseGroupMembershipView(APIView):
237
+ """
238
+ View for getting Group Memberships information for an enterprise.
239
+ """
240
+ authentication_classes = (JwtAuthentication,)
241
+ http_method_names = ["get"]
242
+
243
+ @permission_required("can_access_enterprise", fn=lambda request, enterprise_uuid: enterprise_uuid)
244
+ def get(self, request, enterprise_uuid):
245
+ """
246
+ Returns the groups and budgets for an enterprise.
247
+ """
248
+ groups = EnterpriseGroupMembership.objects.filter(
249
+ enterprise_customer_id=enterprise_uuid,
250
+ group_type='flex',
251
+ )
252
+
253
+ serializer = serializers.EnterpriseGroupMembershipSerializer(groups, many=True)
254
+ return Response(serializer.data)
@@ -12,7 +12,7 @@ from rest_framework.response import Response
12
12
 
13
13
  from django.conf import settings
14
14
  from django.core.paginator import Paginator
15
- from django.db.models import Count, Max, OuterRef, Prefetch, Q, Subquery, Value
15
+ from django.db.models import Count, Exists, Max, OuterRef, Prefetch, Q, Subquery, Value
16
16
  from django.db.models.fields import IntegerField
17
17
  from django.db.models.functions import Coalesce
18
18
  from django.http import StreamingHttpResponse
@@ -20,7 +20,7 @@ from django.utils import timezone
20
20
 
21
21
  from enterprise_data.admin_analytics.database.utils import LOGGER
22
22
  from enterprise_data.api.v1 import serializers
23
- from enterprise_data.models import EnterpriseLearner, EnterpriseLearnerEnrollment
23
+ from enterprise_data.models import EnterpriseGroupMembership, EnterpriseLearner, EnterpriseLearnerEnrollment
24
24
  from enterprise_data.paginators import EnterpriseEnrollmentsPagination
25
25
  from enterprise_data.renderers import EnrollmentsCSVRenderer
26
26
  from enterprise_data.utils import subtract_one_month
@@ -59,9 +59,9 @@ class EnterpriseLearnerEnrollmentViewSet(EnterpriseViewSetMixin, viewsets.ReadOn
59
59
  'course_primary_program', 'primary_program_type', 'course_primary_subject', 'has_passed',
60
60
  'last_activity_date', 'progress_status', 'passed_date', 'current_grade',
61
61
  'letter_grade', 'enterprise_user_id', 'user_email', 'user_account_creation_date',
62
- 'user_country_code', 'user_username', 'enterprise_name', 'enterprise_customer_uuid',
63
- 'enterprise_sso_uid', 'created', 'course_api_url', 'total_learning_time_hours', 'is_subsidy',
64
- 'course_product_line', 'budget_id'
62
+ 'user_country_code', 'user_username', 'user_first_name', 'user_last_name', 'enterprise_name',
63
+ 'enterprise_customer_uuid', 'enterprise_sso_uid', 'created', 'course_api_url', 'total_learning_time_hours',
64
+ 'is_subsidy', 'course_product_line', 'budget_id',
65
65
  ]
66
66
 
67
67
  # TODO: Remove after we release the streaming csv changes
@@ -174,6 +174,15 @@ class EnterpriseLearnerEnrollmentViewSet(EnterpriseViewSetMixin, viewsets.ReadOn
174
174
  if is_subsidy:
175
175
  queryset = queryset.filter(is_subsidy=is_subsidy)
176
176
 
177
+ group_uuid = query_filters.get('group_uuid')
178
+ if group_uuid:
179
+ flex_group_exists = EnterpriseGroupMembership.objects.filter(
180
+ enterprise_customer_user_id=OuterRef('enterprise_user_id'),
181
+ enterprise_group_uuid=group_uuid,
182
+ group_type='flex'
183
+ )
184
+ queryset = queryset.filter(Exists(flex_group_exists))
185
+
177
186
  return queryset
178
187
 
179
188
  def filter_by_offer_id(self, queryset, offer_id):
@@ -39,6 +39,8 @@ class TestCreateEnterpriseLearnerEnrollmentCommand(TestCase):
39
39
  assert enterprise_learner_enrollment[0].letter_grade is None
40
40
  assert enterprise_learner_enrollment[0].enterprise_user_id is None
41
41
  assert enterprise_learner_enrollment[0].user_username is None
42
+ assert enterprise_learner_enrollment[0].user_first_name is None
43
+ assert enterprise_learner_enrollment[0].user_last_name is None
42
44
  assert enterprise_learner_enrollment[0].user_email is None
43
45
  assert enterprise_learner_enrollment[0].enterprise_user is None
44
46
 
@@ -62,6 +64,8 @@ class TestCreateEnterpriseLearnerEnrollmentCommand(TestCase):
62
64
  assert enterprise_learner_enrollment[0].progress_status is not None
63
65
  assert enterprise_learner_enrollment[0].enterprise_user_id is not None
64
66
  assert enterprise_learner_enrollment[0].user_username is not None
67
+ assert enterprise_learner_enrollment[0].user_first_name is not None
68
+ assert enterprise_learner_enrollment[0].user_last_name is not None
65
69
  assert enterprise_learner_enrollment[0].enterprise_user is not None
66
70
  assert enterprise_learner_enrollment[0].user_email is not None
67
71
  assert EnterpriseLearner.objects.count() == 1
@@ -0,0 +1,27 @@
1
+ # Generated by Django 4.2.15 on 2024-12-23 12:55
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('enterprise_data', '0044_enterpriseexecedlcmoduleperformance'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterModelOptions(
14
+ name='enterpriseexecedlcmoduleperformance',
15
+ options={'verbose_name': 'Exec Ed LC Module Performance', 'verbose_name_plural': 'Exec Ed LC Module Performance'},
16
+ ),
17
+ migrations.AddField(
18
+ model_name='enterpriselearnerenrollment',
19
+ name='user_first_name',
20
+ field=models.CharField(max_length=255, null=True),
21
+ ),
22
+ migrations.AddField(
23
+ model_name='enterpriselearnerenrollment',
24
+ name='user_last_name',
25
+ field=models.CharField(max_length=255, null=True),
26
+ ),
27
+ ]
@@ -0,0 +1,36 @@
1
+ # Generated by Django 4.2.16 on 2025-01-08 07:31
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('enterprise_data', '0045_alter_enterpriseexecedlcmoduleperformance_options_and_more'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.CreateModel(
14
+ name='EnterpriseGroupMembership',
15
+ fields=[
16
+ ('group_membership_unique_id', models.CharField(max_length=64, primary_key=True, serialize=False)),
17
+ ('is_applies_to_all_contexts', models.BooleanField(default=False)),
18
+ ('enterprise_customer_id', models.UUIDField(db_index=True, null=True)),
19
+ ('enterprise_group_name', models.CharField(max_length=255, null=True)),
20
+ ('enterprise_group_uuid', models.UUIDField(null=True)),
21
+ ('group_is_removed', models.BooleanField(default=False)),
22
+ ('group_type', models.CharField(max_length=128, null=True)),
23
+ ('activated_at', models.DateTimeField(null=True)),
24
+ ('enterprise_customer_user_id', models.PositiveIntegerField(null=True)),
25
+ ('membership_is_removed', models.BooleanField(default=False)),
26
+ ('membership_status', models.CharField(max_length=128, null=True)),
27
+ ('enterprise_group_membership_uuid', models.UUIDField(null=True)),
28
+ ],
29
+ options={
30
+ 'verbose_name': 'Group Membership',
31
+ 'verbose_name_plural': 'Group Memberships',
32
+ 'db_table': 'group_membership',
33
+ 'indexes': [models.Index(fields=['enterprise_group_uuid', 'group_type', 'enterprise_customer_user_id'], name='group_membe_enterpr_796f48_idx')],
34
+ },
35
+ ),
36
+ ]
enterprise_data/models.py CHANGED
@@ -128,6 +128,8 @@ class EnterpriseLearnerEnrollment(models.Model):
128
128
  user_account_creation_date = models.DateTimeField(null=True)
129
129
  user_country_code = models.CharField(max_length=2, null=True)
130
130
  user_username = models.CharField(max_length=255, null=True)
131
+ user_first_name = models.CharField(max_length=255, null=True)
132
+ user_last_name = models.CharField(max_length=255, null=True)
131
133
  enterprise_name = models.CharField(max_length=255, db_index=True, null=False)
132
134
  enterprise_customer_uuid = models.UUIDField(db_index=True, null=False)
133
135
  enterprise_sso_uid = models.CharField(max_length=255, null=True)
@@ -396,6 +398,50 @@ class EnterpriseSubsidyBudget(models.Model):
396
398
  return self.__str__()
397
399
 
398
400
 
401
+ class EnterpriseGroupMembership(models.Model):
402
+ """
403
+ Details of group memberships in enterprise reports.
404
+ """
405
+
406
+ objects = EnterpriseReportingModelManager()
407
+
408
+ class Meta:
409
+ app_label = 'enterprise_data'
410
+ db_table = 'group_membership'
411
+ verbose_name = _("Group Membership")
412
+ verbose_name_plural = _("Group Memberships")
413
+ indexes = [
414
+ models.Index(fields=['enterprise_group_uuid', 'group_type', 'enterprise_customer_user_id']),
415
+ ]
416
+
417
+ group_membership_unique_id = models.CharField(max_length=64, primary_key=True)
418
+ is_applies_to_all_contexts = models.BooleanField(default=False)
419
+ enterprise_customer_id = models.UUIDField(db_index=True, null=True)
420
+ enterprise_group_name = models.CharField(max_length=255, null=True)
421
+ enterprise_group_uuid = models.UUIDField(null=True)
422
+ group_is_removed = models.BooleanField(default=False)
423
+ group_type = models.CharField(max_length=128, null=True)
424
+ activated_at = models.DateTimeField(null=True)
425
+ enterprise_customer_user_id = models.PositiveIntegerField(null=True)
426
+ membership_is_removed = models.BooleanField(default=False)
427
+ membership_status = models.CharField(max_length=128, null=True)
428
+ enterprise_group_membership_uuid = models.UUIDField(null=True)
429
+
430
+ def __str__(self):
431
+ """
432
+ Return a human-readable string representation of the object.
433
+ """
434
+ return (f'<Enterprise Group Membership: {self.enterprise_group_name} '
435
+ f'(Group UUID: {self.enterprise_group_uuid}) '
436
+ f'for Customer ID {self.enterprise_customer_id}>')
437
+
438
+ def __repr__(self):
439
+ """
440
+ Return uniquely identifying string representation.
441
+ """
442
+ return self.__str__()
443
+
444
+
399
445
  class EnterpriseUser(models.Model):
400
446
  """Information includes a mix of the user's meta data.
401
447
 
@@ -25,9 +25,9 @@ class EnrollmentsCSVRenderer(CSVStreamingRenderer):
25
25
  'course_primary_program', 'primary_program_type', 'course_primary_subject', 'has_passed',
26
26
  'last_activity_date', 'progress_status', 'passed_date', 'current_grade',
27
27
  'letter_grade', 'enterprise_user_id', 'user_email', 'user_account_creation_date',
28
- 'user_country_code', 'user_username', 'enterprise_name', 'enterprise_customer_uuid',
29
- 'enterprise_sso_uid', 'created', 'course_api_url', 'total_learning_time_hours', 'is_subsidy',
30
- 'course_product_line', 'budget_id', 'enterprise_group_name', 'enterprise_group_uuid',
28
+ 'user_country_code', 'user_username', 'user_first_name', 'user_last_name', 'enterprise_name',
29
+ 'enterprise_customer_uuid', 'enterprise_sso_uid', 'created', 'course_api_url', 'total_learning_time_hours',
30
+ 'is_subsidy', 'course_product_line', 'budget_id', 'enterprise_group_name', 'enterprise_group_uuid',
31
31
  ]
32
32
 
33
33
 
@@ -10,6 +10,7 @@ from pytest import mark
10
10
 
11
11
  from enterprise_data.tests.test_utils import (
12
12
  EnterpriseEnrollmentFactory,
13
+ EnterpriseGroupMembershipFactory,
13
14
  EnterpriseOfferFactory,
14
15
  EnterpriseSubsidyBudgetFactory,
15
16
  EnterpriseUserFactory,
@@ -91,6 +92,32 @@ class TestEnterpriseSubsidyBudget(unittest.TestCase):
91
92
  assert expected_str == method(self.enterprise_subsidy_budget)
92
93
 
93
94
 
95
+ @mark.django_db
96
+ @ddt.ddt
97
+ class TestEnterpriseGroupMembership(unittest.TestCase):
98
+ """
99
+ Tests for Enterprise Group Membership model
100
+ """
101
+
102
+ def setUp(self):
103
+ self.enterprise_group_membership = EnterpriseGroupMembershipFactory(
104
+ enterprise_customer_id='ee5e6b3a-069a-4947-bb8d-d2dbc323396c',
105
+ enterprise_group_name='Test Group',
106
+ enterprise_group_uuid='ee5e6b3a-069a-4947-bb8d-d2dbc323396d',
107
+ )
108
+ super().setUp()
109
+
110
+ @ddt.data(str, repr)
111
+ def test_string_conversion(self, method):
112
+ """
113
+ Test conversion to string.
114
+ """
115
+ expected_str = ('<Enterprise Group Membership: Test Group '
116
+ '(Group UUID: ee5e6b3a-069a-4947-bb8d-d2dbc323396d) '
117
+ 'for Customer ID ee5e6b3a-069a-4947-bb8d-d2dbc323396c>')
118
+ assert expected_str == method(self.enterprise_group_membership)
119
+
120
+
94
121
  @mark.django_db
95
122
  @ddt.ddt
96
123
  class TestEnterpriseUser(unittest.TestCase):
@@ -14,6 +14,7 @@ from django.contrib.auth import get_user_model
14
14
 
15
15
  from enterprise_data.models import (
16
16
  EnterpriseEnrollment,
17
+ EnterpriseGroupMembership,
17
18
  EnterpriseLearner,
18
19
  EnterpriseLearnerEnrollment,
19
20
  EnterpriseOffer,
@@ -180,6 +181,8 @@ class EnterpriseLearnerEnrollmentFactory(factory.django.DjangoModelFactory):
180
181
  enterprise_user_id = factory.Sequence(lambda n: n)
181
182
  user_email = factory.lazy_attribute(lambda x: FAKER.email()) # pylint: disable=no-member
182
183
  user_username = factory.Sequence('robot{}'.format)
184
+ user_first_name = factory.Sequence('Robot First {}'.format)
185
+ user_last_name = factory.Sequence('Robot Last {}'.format)
183
186
  user_account_creation_date = factory.lazy_attribute(lambda x: '2018-01-01')
184
187
  user_country_code = factory.lazy_attribute(lambda x: FAKER.country_code())
185
188
  is_subsidy = factory.lazy_attribute(lambda x: FAKER.boolean()) # pylint: disable=no-member
@@ -230,6 +233,8 @@ class EnterpriseLearnerEnrollmentFactory(factory.django.DjangoModelFactory):
230
233
  'user_account_creation_date',
231
234
  'user_country_code',
232
235
  'user_username',
236
+ 'user_first_name',
237
+ 'user_last_name',
233
238
  ]
234
239
  if create and not obj.is_consent_granted:
235
240
  for field in dsc_dependent_fields:
@@ -284,6 +289,27 @@ class EnterpriseSubsidyBudgetFactory(factory.django.DjangoModelFactory):
284
289
  )
285
290
 
286
291
 
292
+ class EnterpriseGroupMembershipFactory(factory.django.DjangoModelFactory):
293
+ """
294
+ EnterpriseGroupMembership model Factory.
295
+ """
296
+ class Meta:
297
+ model = EnterpriseGroupMembership
298
+
299
+ enterprise_customer_id = factory.LazyAttribute(lambda _: FAKER.uuid4())
300
+ enterprise_group_name = factory.LazyAttribute(lambda _: ' '.join(FAKER.words(nb=2)).title())
301
+ enterprise_group_uuid = factory.LazyAttribute(lambda _: FAKER.uuid4())
302
+ group_is_removed = factory.LazyAttribute(lambda _: FAKER.boolean())
303
+ group_type = factory.LazyAttribute(lambda _: 'budget')
304
+ activated_at = factory.LazyAttribute(lambda _: FAKER.date_time_this_decade(tzinfo=pytz.UTC))
305
+ enterprise_customer_user_id = factory.LazyAttribute(lambda _: FAKER.random_int(min=1, max=10000))
306
+ membership_is_removed = factory.LazyAttribute(lambda _: FAKER.boolean())
307
+ membership_status = factory.LazyAttribute(lambda _: FAKER.word())
308
+ enterprise_group_membership_uuid = factory.LazyAttribute(lambda _: FAKER.uuid4())
309
+
310
+ is_applies_to_all_contexts = factory.LazyAttribute(lambda _: FAKER.boolean())
311
+
312
+
287
313
  class EnterpriseOfferFactory(factory.django.DjangoModelFactory):
288
314
  """
289
315
  EnterpriseLearner model Factory.