Skip to content

Commit

Permalink
feat: allowing for sorting and filtering of the enterprise group lear…
Browse files Browse the repository at this point in the history
…ner endpoints
  • Loading branch information
alex-sheehan-edx committed Apr 8, 2024
1 parent 2abef1a commit 724529c
Show file tree
Hide file tree
Showing 11 changed files with 391 additions and 55 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ Change Log
Unreleased
----------
[4.15.1]
--------
* feat: allowing for sorting and filtering of the enterprise group learner endpoints

[4.15.0]
---------
* feat: Add new languages in enterprise customer admin
Expand Down
2 changes: 1 addition & 1 deletion enterprise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
Your project description goes here.
"""

__version__ = "4.15.0"
__version__ = "4.15.1"
29 changes: 17 additions & 12 deletions enterprise/api/v1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@ class EnterpriseGroupMembershipSerializer(serializers.ModelSerializer):

member_details = serializers.SerializerMethodField()
recent_action = serializers.SerializerMethodField()
member_status = serializers.SerializerMethodField()
status = serializers.CharField(required=False)

class Meta:
model = models.EnterpriseGroupMembership
Expand All @@ -613,7 +613,7 @@ class Meta:
'enterprise_group_membership_uuid',
'member_details',
'recent_action',
'member_status',
'status',
)

def get_member_details(self, obj):
Expand All @@ -634,16 +634,6 @@ def get_recent_action(self, obj):
return f"Accepted: {obj.activated_at.strftime('%B %d, %Y')}"
return f"Invited: {obj.created.strftime('%B %d, %Y')}"

def get_member_status(self, obj):
"""
Return the status related to the membership.
"""
if obj.is_removed:
return "removed"
if obj.enterprise_customer_user:
return "accepted"
return "pending"


class EnterpriseCustomerUserReadOnlySerializer(serializers.ModelSerializer):
"""
Expand Down Expand Up @@ -1700,3 +1690,18 @@ class Meta:
client_secret = serializers.CharField(read_only=True, default=generate_client_secret())
redirect_uris = serializers.CharField(required=False)
updated = serializers.DateTimeField(required=False, read_only=True)


class EnterpriseGroupLearnersRequestQuerySerializer(serializers.Serializer):
"""
Serializer for the Enterprise Group Learners endpoint query filter
"""
user_query = serializers.CharField(required=False, max_length=320)
sort_by = serializers.ChoiceField(
choices=[
('member_details', 'member_details'),
('status', 'status'),
('recent_action', 'recent_action')
],
required=False,
)
37 changes: 32 additions & 5 deletions enterprise/api/v1/views/enterprise_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from django.db.models import Q
from django.http import Http404

from enterprise import models, rules, utils
from enterprise import constants, models, rules, utils
from enterprise.api.utils import get_enterprise_customer_from_enterprise_group_id
from enterprise.api.v1 import serializers
from enterprise.api.v1.views.base_views import EnterpriseReadWriteModelViewSet
Expand Down Expand Up @@ -101,6 +101,13 @@ def get_learners(self, *args, **kwargs):
Request Arguments:
- ``group_uuid`` (URL location, required): The uuid of the group from which learners should be listed.
Optional query params:
- ``q`` (string, optional): Filter the returned members by user email and name with a provided sub-string
- ``sort_by`` (string, optional): Specify how the returned members should be ordered. Supported sorting values
are `memberDetails`, `memberStatus`, and `recentAction`. Ordering can be reversed by supplying a `-` at the
beginning of the sorting value ie `-memberStatus`.
- ``page`` (int, optional): Which page of paginated data to return.
Returns: Paginated list of learners that are associated with the enterprise group uuid::
{
Expand All @@ -117,11 +124,23 @@ def get_learners(self, *args, **kwargs):
}
"""
query_params = self.request.query_params.copy()
is_reversed = bool(query_params.get('is_reversed', False))

param_serializers = serializers.EnterpriseGroupLearnersRequestQuerySerializer(
data=query_params
)

if not param_serializers.is_valid():
return Response(param_serializers.errors, status=400)

user_query = param_serializers.validated_data.get('user_query')
sort_by = param_serializers.validated_data.get('sort_by')

group_uuid = kwargs.get('group_uuid')
try:
group_object = self.get_queryset().get(uuid=group_uuid)
members = group_object.get_all_learners()
members = group_object.get_all_learners(user_query, sort_by, desc_order=is_reversed)
page = self.paginate_queryset(members)
serializer = serializers.EnterpriseGroupMembershipSerializer(page, many=True)
response = self.get_paginated_response(serializer.data)
Expand Down Expand Up @@ -197,6 +216,7 @@ def assign_learners(self, request, group_uuid):
ent_customer_users = [
models.EnterpriseGroupMembership(
activated_at=localized_utcnow(),
status=constants.GROUP_MEMBERSHIP_ACCEPTED_STATUS,
enterprise_customer_user=ecu,
group=group
)
Expand Down Expand Up @@ -275,8 +295,15 @@ def remove_learners(self, request, group_uuid):
)
records_deleted += len(records_to_delete)
records_to_delete.delete()
data = {
'records_deleted': records_deleted,
}

# Woohoo! Records removed! Now to update the soft deleted records
deleted_records = models.EnterpriseGroupMembership.all_objects.filter(
group_q & (ecu_in_q | pecu_in_q),
)
deleted_records.update(
status=constants.GROUP_MEMBERSHIP_REMOVED_STATUS,
removed_at=localized_utcnow()
)
data = {'records_deleted': records_deleted}
return Response(data, status=200)
return Response(data="Error: missing request data: `learner_emails`.", status=400)
9 changes: 9 additions & 0 deletions enterprise/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,12 @@ class FulfillmentTypes:

# The maximum length of a text field in the database.
MAX_ALLOWED_TEXT_LENGTH = 16_000_000

GROUP_MEMBERSHIP_PENDING_STATUS = 'pending'
GROUP_MEMBERSHIP_REMOVED_STATUS = 'removed'
GROUP_MEMBERSHIP_ACCEPTED_STATUS = 'accepted'
GROUP_MEMBERSHIP_STATUS_CHOICES = (
(GROUP_MEMBERSHIP_REMOVED_STATUS, 'Removed'),
(GROUP_MEMBERSHIP_ACCEPTED_STATUS, 'Accepted'),
(GROUP_MEMBERSHIP_PENDING_STATUS, 'Pending'),
)
10 changes: 2 additions & 8 deletions enterprise/management/commands/manufacture_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"""

import logging
import re
import sys

import factory
Expand All @@ -13,6 +12,8 @@
from django.core.management.base import BaseCommand, CommandError, SystemCheckError, handle_default_options
from django.db import connections

from enterprise.utils import convert_to_snake

# We have to import the enterprise test factories to ensure it's loaded and found by __subclasses__
# To ensure factories outside of the enterprise package are loaded and found by the script,
# add any additionally desired factories as an import to this file. Make sure to catch the ImportError
Expand Down Expand Up @@ -53,13 +54,6 @@ def all_subclasses(cls):
[s for c in cls.__subclasses__() for s in all_subclasses(c)])


def convert_to_snake(string):
"""
Helper method to convert strings to snake case.
"""
return re.sub(r'(?<!^)(?=[A-Z])', '_', string).lower()


class Node():
"""
Non-binary tree node class for building out a dependency tree of objects to create with customizations.
Expand Down
33 changes: 33 additions & 0 deletions enterprise/migrations/0206_auto_20240408_1344.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 3.2.23 on 2024-04-08 13:44

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('enterprise', '0205_auto_20240408_1117'),
]

operations = [
migrations.AddField(
model_name='enterprisegroupmembership',
name='removed_at',
field=models.DateTimeField(blank=True, default=None, help_text='The moment at which the membership record was revoked by an Enterprise admin.', null=True),
),
migrations.AddField(
model_name='enterprisegroupmembership',
name='status',
field=models.CharField(blank=True, choices=[('removed', 'Removed'), ('accepted', 'Accepted'), ('pending', 'Pending')], default='pending', help_text='Current status of the membership record', max_length=20, null=True, verbose_name='Membership Status'),
),
migrations.AddField(
model_name='historicalenterprisegroupmembership',
name='removed_at',
field=models.DateTimeField(blank=True, default=None, help_text='The moment at which the membership record was revoked by an Enterprise admin.', null=True),
),
migrations.AddField(
model_name='historicalenterprisegroupmembership',
name='status',
field=models.CharField(blank=True, choices=[('removed', 'Removed'), ('accepted', 'Accepted'), ('pending', 'Pending')], default='pending', help_text='Current status of the membership record', max_length=20, null=True, verbose_name='Membership Status'),
),
]
Loading

0 comments on commit 724529c

Please sign in to comment.