From 64b24fd52506552ada1edeaaa1d63b8b5d48c8a2 Mon Sep 17 00:00:00 2001 From: Kira Miller <31229189+kiram15@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:27:24 -0600 Subject: [PATCH 1/2] fix: removing applies_to_all_context EnterpriseGroup references (#2268) * fix: removing instances of old variable * fix: removing migration file --- CHANGELOG.rst | 4 +++ enterprise/__init__.py | 2 +- enterprise/admin/__init__.py | 4 +-- enterprise/api/v1/serializers.py | 13 +-------- enterprise/models.py | 8 ----- test_utils/factories.py | 1 - tests/test_enterprise/api/test_serializers.py | 15 ---------- tests/test_enterprise/api/test_views.py | 29 ------------------- 8 files changed, 8 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 980987313..dcd2dfba3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,10 @@ Unreleased ---------- * nothing unreleased +[4.28.3] +-------- +* feat: removing all references of to-be-deleted field + [4.28.2] -------- * fix: added content_title, progress_status in get_learner_data_records for derived classed of learner data exporters. diff --git a/enterprise/__init__.py b/enterprise/__init__.py index 8d04af17d..1ce69bd81 100644 --- a/enterprise/__init__.py +++ b/enterprise/__init__.py @@ -2,4 +2,4 @@ Your project description goes here. """ -__version__ = "4.28.2" +__version__ = "4.28.3" diff --git a/enterprise/admin/__init__.py b/enterprise/admin/__init__.py index 70058ffb5..66bb01aaf 100644 --- a/enterprise/admin/__init__.py +++ b/enterprise/admin/__init__.py @@ -1221,8 +1221,8 @@ class EnterpriseGroupAdmin(admin.ModelAdmin): Django admin for EnterpriseGroup model. """ model = models.EnterpriseGroup - list_display = ('uuid', 'enterprise_customer', 'applies_to_all_contexts', ) - list_filter = ('applies_to_all_contexts',) + list_display = ('uuid', 'enterprise_customer', ) + list_filter = ('group_type',) search_fields = ( 'uuid', 'name', diff --git a/enterprise/api/v1/serializers.py b/enterprise/api/v1/serializers.py index 4bef00173..75fc918eb 100644 --- a/enterprise/api/v1/serializers.py +++ b/enterprise/api/v1/serializers.py @@ -638,7 +638,7 @@ class EnterpriseGroupSerializer(serializers.ModelSerializer): class Meta: model = models.EnterpriseGroup fields = ( - 'enterprise_customer', 'name', 'uuid', 'applies_to_all_contexts', + 'enterprise_customer', 'name', 'uuid', 'accepted_members_count', 'group_type', 'created') accepted_members_count = serializers.SerializerMethodField() @@ -786,22 +786,11 @@ def get_enterprise_group(self, obj): """ Return the enterprise group membership for this enterprise customer user. """ - related_customer = obj.enterprise_customer - # Find any groups that have ``applies_to_all_contexts`` set to True that are connected to the customer - # that's related to the customer associated with this customer user record. - all_context_groups = models.EnterpriseGroup.objects.filter( - enterprise_customer=related_customer, - applies_to_all_contexts=True - ).values_list('uuid', flat=True) enterprise_groups_from_memberships = obj.memberships.select_related('group').all().values_list( 'group', flat=True ) - # Combine both sets of group UUIDs group_uuids = set(enterprise_groups_from_memberships) - for group in all_context_groups: - group_uuids.add(group) - return list(group_uuids) diff --git a/enterprise/models.py b/enterprise/models.py index 71a5cac19..e3f616f6a 100644 --- a/enterprise/models.py +++ b/enterprise/models.py @@ -4497,14 +4497,6 @@ class EnterpriseGroup(TimeStampedModel, SoftDeletableModel): related_name='groups', on_delete=models.deletion.CASCADE ) - applies_to_all_contexts = models.BooleanField( - verbose_name="Set group membership to the entire org of learners.", - default=False, - null=True, - help_text=_( - "When enabled, all learners connected to the org will be considered a member." - ) - ) group_type = models.CharField( verbose_name="Group Type", max_length=20, diff --git a/test_utils/factories.py b/test_utils/factories.py index 2a2089fbf..4adcef200 100644 --- a/test_utils/factories.py +++ b/test_utils/factories.py @@ -1115,7 +1115,6 @@ class Meta: model = EnterpriseGroup uuid = factory.LazyAttribute(lambda x: UUID(FAKER.uuid4())) - applies_to_all_contexts = False enterprise_customer = factory.SubFactory(EnterpriseCustomerFactory) name = factory.LazyAttribute(lambda x: FAKER.company()) diff --git a/tests/test_enterprise/api/test_serializers.py b/tests/test_enterprise/api/test_serializers.py index aa330a356..bdb214225 100644 --- a/tests/test_enterprise/api/test_serializers.py +++ b/tests/test_enterprise/api/test_serializers.py @@ -314,21 +314,6 @@ def test_group_membership(self): assert len(serializer.data['enterprise_group']) == 1 assert serializer.data['enterprise_group'][0] == membership.group.uuid - def test_group_membership_when_applies_to_all_contexts(self): - """ - Test that when a group has ``applies_to_all_contexts`` set to True, that group is included in the enterprise - customer user serializer data when there is an associated via an enterprise customer object. - """ - enterprise_group = factories.EnterpriseGroupFactory( - enterprise_customer=self.enterprise_customer_1, - applies_to_all_contexts=True, - ) - serializer = EnterpriseCustomerUserReadOnlySerializer(self.enterprise_customer_user_1) - # Assert the enterprise customer user serializer found the group - assert serializer.data.get('enterprise_group') == [enterprise_group.uuid] - # Assert the group has no memberships that could be read by the serializer - assert not enterprise_group.members.all() - def test_multi_group_membership(self): """ Test that multiple group memberships are associated properly with a single instance. diff --git a/tests/test_enterprise/api/test_views.py b/tests/test_enterprise/api/test_views.py index 8c7fd276b..4e50bba26 100644 --- a/tests/test_enterprise/api/test_views.py +++ b/tests/test_enterprise/api/test_views.py @@ -8145,7 +8145,6 @@ def test_list_learners_filtered(self): """ group = EnterpriseGroupFactory( enterprise_customer=self.enterprise_customer, - applies_to_all_contexts=True, ) pending_user = PendingEnterpriseCustomerUserFactory( user_email="foobar@example.com", @@ -8160,7 +8159,6 @@ def test_list_learners_filtered(self): assert response.json().get('count') == 0 - group.applies_to_all_contexts = False group.save() pending_membership = EnterpriseGroupMembershipFactory( group=group, @@ -8203,7 +8201,6 @@ def test_list_learners_filtered(self): def test_list_removed_learners(self): group = EnterpriseGroupFactory( enterprise_customer=self.enterprise_customer, - applies_to_all_contexts=False, ) memberships_to_delete = [] membership = EnterpriseGroupMembershipFactory( @@ -8895,32 +8892,6 @@ def test_remove_learners_from_group_only_removes_from_specified_group(self): EnterpriseGroupMembership.objects.get(pk=membership_to_remove.pk) assert EnterpriseGroupMembership.objects.get(pk=existing_membership.pk) - def test_group_applies_to_all_contexts_learner_list(self): - """ - Test that hitting the enterprise-group `/learners/` endpoint for a group that has ``applies_to_all_contexts`` - will return all learners in the group's org regardless of what membership records exist. - """ - new_group = EnterpriseGroupFactory(applies_to_all_contexts=True) - new_user = EnterpriseCustomerUserFactory( - user_id=self.user.id, enterprise_customer=new_group.enterprise_customer, - active=True - ) - pending_user = PendingEnterpriseCustomerUserFactory( - enterprise_customer=new_group.enterprise_customer, - ) - url = settings.TEST_SERVER + reverse( - 'enterprise-group-learners', - kwargs={'group_uuid': new_group.uuid}, - ) - response = self.client.get(url) - results = response.json().get('results') - for result in results: - assert ( - result.get('pending_enterprise_customer_user_id') == pending_user.id - ) or ( - result.get('enterprise_customer_user_id') == new_user.id - ) - def test_group_assign_realized_learner_adds_activated_at(self): """ Test that newly created membership records associated with an existing user have an activated at value written From aa0cf5ff73fff7c59a7a0a2b943c288d5f376c85 Mon Sep 17 00:00:00 2001 From: Kira Miller Date: Wed, 23 Oct 2024 20:26:54 +0000 Subject: [PATCH 2/2] fix: in progress commit --- enterprise/api/v1/serializers.py | 28 +++++ enterprise/api/v1/urls.py | 8 ++ .../v1/views/enterprise_customer_members.py | 111 ++++++++++++++++++ tests/test_enterprise/api/test_serializers.py | 43 ++++++- 4 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 enterprise/api/v1/views/enterprise_customer_members.py diff --git a/enterprise/api/v1/serializers.py b/enterprise/api/v1/serializers.py index 4bef00173..5d628234d 100644 --- a/enterprise/api/v1/serializers.py +++ b/enterprise/api/v1/serializers.py @@ -1905,3 +1905,31 @@ def get_role_assignments(self, obj): return role_assignments_by_ecu_id else: return None + +class EnterpriseMemberSerializer(serializers.Serializer): + """ + Serializer for EnterpriseCustomerUser model with additions. + """ + class Meta: + model = models.EnterpriseCustomerUser + fields = ( + 'enterprise_customer_user', + # 'user_email', + # 'enrollments', + # 'created', + ) + enterprise_customer_user = UserSerializer() + # user_email = serializers.EmailField() + # enrollments = serializers.SerializerMethodField() + + + def get_enrollments(self, obj): + """ + Fetch all of user's enterprise enrollments + """ + if hasattr(obj, 'user_id'): + user_id = obj.user_id + enrollments = models.EnterpriseCourseEnrollment.objects.filter( + enterprise_customer_user=user_id, + ) + return len(enrollments) diff --git a/enterprise/api/v1/urls.py b/enterprise/api/v1/urls.py index 866ed4292..168f5a066 100644 --- a/enterprise/api/v1/urls.py +++ b/enterprise/api/v1/urls.py @@ -16,6 +16,7 @@ enterprise_customer_branding_configuration, enterprise_customer_catalog, enterprise_customer_invite_key, + enterprise_customer_members, enterprise_customer_reporting, enterprise_customer_sso_configuration, enterprise_customer_support, @@ -205,6 +206,13 @@ ), name='enterprise-customer-support' ), + re_path( + r'^enterprise-customer-members/(?P[A-Za-z0-9-]+)$', + enterprise_customer_members.EnterpriseCustomerMembersViewSet.as_view( + {'get': 'get_members'} + ), + name='enterprise-customer-members' + ), ] urlpatterns += router.urls diff --git a/enterprise/api/v1/views/enterprise_customer_members.py b/enterprise/api/v1/views/enterprise_customer_members.py new file mode 100644 index 000000000..ddbba015f --- /dev/null +++ b/enterprise/api/v1/views/enterprise_customer_members.py @@ -0,0 +1,111 @@ +""" +Views for the ``enterprise-customer-members`` API endpoint. +""" + +from collections import OrderedDict + +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import filters, permissions, response, status +from rest_framework.pagination import PageNumberPagination + +from django.contrib import auth +from django.core.exceptions import ValidationError +from django.db.models import Q + +from enterprise import models +from enterprise.api.v1 import serializers +from enterprise.api.v1.views.base_views import EnterpriseReadOnlyModelViewSet +from enterprise.logging import getEnterpriseLogger + +User = auth.get_user_model() + +LOGGER = getEnterpriseLogger(__name__) + +class EnterpriseCustomerMembersPaginator(PageNumberPagination): + """Custom paginator for the enterprise customer members.""" + + page_size = 6 + + def get_paginated_response(self, data): + """Return a paginated style `Response` object for the given output data.""" + return response.Response( + OrderedDict( + [ + ("count", self.page.paginator.count), + ("num_pages", self.page.paginator.num_pages), + ("next", self.get_next_link()), + ("previous", self.get_previous_link()), + ("results", data), + ] + ) + ) + + def paginate_queryset(self, queryset, request, view=None): + """ + Paginate a queryset if required, either returning a page object, + or `None` if pagination is not configured for this view. + + """ + if isinstance(queryset, filter): + queryset = list(queryset) + + return super().paginate_queryset(queryset, request, view) + + +class EnterpriseCustomerMembersViewSet(EnterpriseReadOnlyModelViewSet): + """ + API views for the ``enterprise-customer-members`` API endpoint. + """ + + queryset = models.PendingEnterpriseCustomerUser.objects.all() + filter_backends = (DjangoFilterBackend, filters.OrderingFilter) + permission_classes = (permissions.IsAuthenticated,) + paginator = EnterpriseCustomerMembersPaginator() + + def filter_queryset_by_user_query(self, queryset, is_pending_user=False): + """ + Filter queryset based on user provided query + """ + user_query = self.request.query_params.get("user_query", None) + if user_query: + queryset = models.EnterpriseCustomerUser.objects.filter( + user_id__in=User.objects.filter( + Q(email__icontains=user_query) | Q(username__icontains=user_query) + ) + ) + return queryset + + def get_members(self, request, *args, **kwargs): + """ + Filter down the queryset of groups available to the requesting uuid. + """ + enterprise_uuid = kwargs.get("enterprise_uuid", None) + users = [] + + try: + enterprise_customer_queryset = models.EnterpriseCustomerUser.objects.filter( + enterprise_customer__uuid=enterprise_uuid, + ) + enterprise_customer_queryset = self.filter_queryset_by_user_query( + enterprise_customer_queryset + ) + users.extend(enterprise_customer_queryset) + + except ValidationError: + # did not find UUID match in either EnterpriseCustomerUser + return response.Response( + {"detail": "Could not find enterprise uuid {}".format(enterprise_uuid)}, + status=status.HTTP_404_NOT_FOUND, + ) + + # default sort criteria + is_reversed = False + + # paginate the queryset + users_page = self.paginator.paginate_queryset(users, request, view=self) + + # serialize the paged dataset + serializer = serializers.EnterpriseMemberSerializer(users_page, many=True) + serializer_data = serializer.data + + return self.paginator.get_paginated_response(serializer_data) diff --git a/tests/test_enterprise/api/test_serializers.py b/tests/test_enterprise/api/test_serializers.py index aa330a356..77a76f168 100644 --- a/tests/test_enterprise/api/test_serializers.py +++ b/tests/test_enterprise/api/test_serializers.py @@ -19,6 +19,7 @@ EnterpriseCustomerReportingConfigurationSerializer, EnterpriseCustomerSerializer, EnterpriseCustomerUserReadOnlySerializer, + EnterpriseMemberSerializer, EnterpriseUserSerializer, ImmutableStateSerializer, ) @@ -470,7 +471,7 @@ def setUp(self): super().setUp() - # setup Enteprise Customer + # setup Enterprise Customer self.user_1 = factories.UserFactory() self.user_2 = factories.UserFactory() self.enterprise_customer_user_1 = factories.EnterpriseCustomerUserFactory(user_id=self.user_1.id) @@ -573,3 +574,43 @@ def test_serialize_pending_users(self): serialized_pending_admin_user = serializer.data self.assertEqual(expected_pending_admin_user, serialized_pending_admin_user) + +@mark.django_db +class TestEnterpriseUserSerializer(TestCase): + def setUp(self): + """ + Perform operations common for all tests. + """ + super().setUp() + + # setup Enterprise Customer + self.user_1 = factories.UserFactory() + self.user_2 = factories.UserFactory() + self.enterprise_customer_user_1 = factories.EnterpriseCustomerUserFactory(user_id=self.user_1.id) + self.enterprise_customer_user_2 = factories.EnterpriseCustomerUserFactory(user_id=self.user_2.id) + self.enterprise_customer_1 = self.enterprise_customer_user_1.enterprise_customer + self.enterprise_customer_2 = self.enterprise_customer_user_2.enterprise_customer + + self.enrollment_1 = factories.EnterpriseCourseEnrollmentFactory( + enterprise_customer_user=self.enterprise_customer_user_1, + ) + self.enrollment_1 = factories.EnterpriseCourseEnrollmentFactory( + enterprise_customer_user=self.enterprise_customer_user_1, + ) + self.enrollment_1 = factories.EnterpriseCourseEnrollmentFactory( + enterprise_customer_user=self.enterprise_customer_user_2, + ) + + def test_serialize_users(self): + for customer_user in [ + (self.enterprise_customer_user_1), + (self.enterprise_customer_user_2), + ]: + user = customer_user.user + serializer = EnterpriseMemberSerializer(customer_user) + print("serializer ", serializer) + print("data ", serializer.data) + serialized_user = serializer.data + + self.assertEqual('', serialized_user) +