Skip to content

Commit d37727e

Browse files
Inactive contracts should not appear in API results by default (#3074)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 79a3e86 commit d37727e

File tree

3 files changed

+63
-4
lines changed

3 files changed

+63
-4
lines changed

b2b/serializers/v0/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,13 @@ class OrganizationPageSerializer(serializers.ModelSerializer):
5454
Serializer for the OrganizationPage model.
5555
"""
5656

57-
contracts = ContractPageSerializer(many=True, read_only=True)
57+
contracts = serializers.SerializerMethodField()
58+
59+
@extend_schema_field(ContractPageSerializer(many=True))
60+
def get_contracts(self, instance):
61+
"""Get only active contracts for the organization"""
62+
active_contracts = instance.contracts.filter(active=True)
63+
return ContractPageSerializer(active_contracts, many=True).data
5864

5965
class Meta:
6066
model = OrganizationPage
@@ -145,6 +151,7 @@ def get_contracts(self, instance):
145151
self.context["user"]
146152
.b2b_contracts.filter(
147153
organization=instance.organization,
154+
active=True,
148155
)
149156
.all()
150157
)

b2b/views/v0/__init__.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,15 @@ class ContractPageViewSet(viewsets.ReadOnlyModelViewSet):
4747
Viewset for the ContractPage model.
4848
"""
4949

50-
queryset = ContractPage.objects.all()
5150
serializer_class = ContractPageSerializer
5251
permission_classes = [IsAdminOrReadOnly | HasAPIKey]
5352
lookup_field = "slug"
5453
lookup_url_kwarg = "contract_slug"
5554

55+
def get_queryset(self):
56+
"""Filter to only return active contracts by default."""
57+
return ContractPage.objects.filter(active=True)
58+
5659

5760
class Enroll(APIView):
5861
"""View for enrolling in a B2B course."""
@@ -118,6 +121,13 @@ def post(self, request, enrollment_code: str, format=None): # noqa: A002, ARG00
118121
"""
119122

120123
now = now_in_utc()
124+
125+
def get_active_user_contracts(user):
126+
"""Helper to get active contracts for a user."""
127+
return user.b2b_contracts.filter(active=True).exclude(
128+
Q(contract_start__gt=now) | Q(contract_end__lt=now)
129+
)
130+
121131
try:
122132
code = (
123133
Discount.objects.annotate(Count("contract_redemptions"))
@@ -131,7 +141,9 @@ def post(self, request, enrollment_code: str, format=None): # noqa: A002, ARG00
131141
)
132142
except Discount.DoesNotExist:
133143
return Response(
134-
ContractPageSerializer(request.user.b2b_contracts.all(), many=True).data
144+
ContractPageSerializer(
145+
get_active_user_contracts(request.user), many=True
146+
).data
135147
)
136148

137149
contract_ids = list(code.b2b_contracts().values_list("id", flat=True))
@@ -157,5 +169,7 @@ def post(self, request, enrollment_code: str, format=None): # noqa: A002, ARG00
157169
request.user.save()
158170

159171
return Response(
160-
ContractPageSerializer(request.user.b2b_contracts.all(), many=True).data
172+
ContractPageSerializer(
173+
get_active_user_contracts(request.user), many=True
174+
).data
161175
)

users/views_test.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,44 @@ def test_get_user_by_me(mocker, client, user, is_anonymous, has_orgs):
146146
}
147147

148148

149+
@pytest.mark.django_db
150+
def test_get_user_by_me_excludes_inactive_contracts(client, user):
151+
"""Test that /api/v0/users/me only returns active contracts"""
152+
client.force_login(user)
153+
154+
# Create active and inactive contracts in the same organization
155+
active_contract = ContractPageFactory.create(active=True)
156+
inactive_contract = ContractPageFactory.create(
157+
active=False, organization=active_contract.organization
158+
)
159+
160+
# Add user to the organization
161+
user.b2b_organizations.add(active_contract.organization)
162+
# Add user to both contracts
163+
user.b2b_contracts.add(active_contract)
164+
user.b2b_contracts.add(inactive_contract)
165+
user.save()
166+
167+
resp = client.get(reverse("users_api-me"))
168+
169+
assert resp.status_code == status.HTTP_200_OK
170+
171+
response_data = resp.json()
172+
assert len(response_data["b2b_organizations"]) == 1
173+
174+
org_data = response_data["b2b_organizations"][0]
175+
assert org_data["id"] == active_contract.organization.id
176+
177+
# Should only include the active contract
178+
assert len(org_data["contracts"]) == 1
179+
assert org_data["contracts"][0]["id"] == active_contract.id
180+
assert org_data["contracts"][0]["active"] is True
181+
182+
# Should not include the inactive contract
183+
contract_ids = [contract["id"] for contract in org_data["contracts"]]
184+
assert inactive_contract.id not in contract_ids
185+
186+
149187
@pytest.mark.parametrize(
150188
("is_anonymous", "has_openedx_user", "has_edx_username"),
151189
[

0 commit comments

Comments
 (0)