Skip to content

Commit 6cc708c

Browse files
authored
Add API key for B2B data (#2660)
1 parent 646bd03 commit 6cc708c

File tree

7 files changed

+128
-33
lines changed

7 files changed

+128
-33
lines changed

b2b/management/commands/b2b_list.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import logging
44

5-
from django.core.management import BaseCommand
5+
from django.core.management import BaseCommand, CommandError
66
from rich.console import Console
77
from rich.table import Table
88

@@ -68,6 +68,24 @@ def add_arguments(self, parser):
6868
dest="contract_id",
6969
)
7070

71+
learners_parser = subparsers.add_parser(
72+
"learners",
73+
help="List learners associated with an org or contract.",
74+
)
75+
learners_parser.add_argument(
76+
"--org",
77+
"--organization",
78+
type=int,
79+
help="Filter by organization ID.",
80+
dest="organization_id",
81+
)
82+
learners_parser.add_argument(
83+
"--contract",
84+
type=int,
85+
help="Filter by contract ID.",
86+
dest="contract_id",
87+
)
88+
7189
return super().add_arguments(parser)
7290

7391
def handle_list_orgs(self, *args, **kwargs): # noqa: ARG002
@@ -180,6 +198,33 @@ def handle_list_courseware(self, *args, **kwargs): # noqa: ARG002
180198

181199
self.console.print(courseware_table)
182200

201+
def handle_list_learners(self, *args, **kwargs): # noqa: ARG002
202+
"""List learners associated with an org or contract."""
203+
204+
org_id = kwargs.pop("organization_id")
205+
contract_id = kwargs.pop("contract_id")
206+
207+
if org_id:
208+
obj = OrganizationPage.objects.get(id=org_id)
209+
elif contract_id:
210+
obj = ContractPage.objects.get(id=contract_id)
211+
else:
212+
msg = "Must provide either org or contract ID."
213+
raise CommandError(msg)
214+
215+
learners = obj.get_learners()
216+
217+
learners_table = Table(title=f"Learners for {obj}")
218+
learners_table.add_column("Email", justify="right")
219+
learners_table.add_column("Name", justify="left")
220+
221+
for learner in learners:
222+
learners_table.add_row(
223+
learner.email,
224+
f"{learner.first_name} {learner.last_name}",
225+
)
226+
self.console.print(learners_table)
227+
183228
def handle(self, *args, **kwargs): # noqa: ARG002
184229
"""Handle the command."""
185230
self.console = Console()
@@ -190,7 +235,10 @@ def handle(self, *args, **kwargs): # noqa: ARG002
190235
self.handle_list_contracts(**kwargs)
191236
elif subcommand == "courseware":
192237
self.handle_list_courseware(**kwargs)
238+
elif subcommand == "learners":
239+
self.handle_list_learners(**kwargs)
193240
else:
194-
log.error("Unknown subcommand: %s", subcommand)
195-
return 1
241+
msg = f"Unknown subcommand: {subcommand}"
242+
raise CommandError(msg)
243+
196244
return 0

b2b/models.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Models for B2B data."""
22

3+
from django.contrib.auth import get_user_model
34
from django.db import models
45
from django.http import Http404
56
from django.utils.text import slugify
@@ -79,6 +80,17 @@ def save(self, clean=True, user=None, log_action=False, **kwargs): # noqa: FBT0
7980
self.slug = slugify(f"org-{self.name}")
8081
Page.save(self, clean=clean, user=user, log_action=log_action, **kwargs)
8182

83+
def get_learners(self):
84+
"""Get the learners associated with this organization."""
85+
86+
return (
87+
get_user_model()
88+
.objects.filter(
89+
b2b_contracts__organization=self,
90+
)
91+
.distinct()
92+
)
93+
8294

8395
class ContractPage(Page):
8496
"""Stores information about a contract with an organization."""
@@ -150,3 +162,14 @@ def save(self, clean=True, user=None, log_action=False, **kwargs): # noqa: FBT0
150162

151163
self.slug = slugify(f"contract-{self.organization.id}-{self.id}")
152164
Page.save(self, clean=clean, user=user, log_action=log_action, **kwargs)
165+
166+
def get_learners(self):
167+
"""Get the learners associated with this organization."""
168+
169+
return (
170+
get_user_model()
171+
.objects.filter(
172+
b2b_contracts=self,
173+
)
174+
.distinct()
175+
)

b2b/views/v0/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from rest_framework import viewsets
44
from rest_framework.permissions import IsAdminUser
5+
from rest_framework_api_key.permissions import HasAPIKey
56

67
from b2b.models import ContractPage, OrganizationPage
78
from b2b.serializers.v0 import ContractPageSerializer, OrganizationPageSerializer
@@ -14,7 +15,7 @@ class OrganizationPageViewSet(viewsets.ReadOnlyModelViewSet):
1415

1516
queryset = OrganizationPage.objects.all()
1617
serializer_class = OrganizationPageSerializer
17-
permission_classes = [IsAdminUser]
18+
permission_classes = [IsAdminUser | HasAPIKey]
1819
lookup_field = "slug"
1920
lookup_url_kwarg = "organization_slug"
2021

@@ -26,6 +27,6 @@ class ContractPageViewSet(viewsets.ReadOnlyModelViewSet):
2627

2728
queryset = ContractPage.objects.all()
2829
serializer_class = ContractPageSerializer
29-
permission_classes = [IsAdminUser]
30+
permission_classes = [IsAdminUser | HasAPIKey]
3031
lookup_field = "slug"
3132
lookup_url_kwarg = "contract_slug"

main/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@
228228
"health_check.contrib.celery_ping",
229229
"health_check.contrib.redis",
230230
"health_check.contrib.db_heartbeat",
231+
"rest_framework_api_key",
231232
)
232233
# Only include the seed data app if this isn't running in prod
233234
# if ENVIRONMENT not in ("production", "prod"):

poetry.lock

Lines changed: 45 additions & 27 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ uwsgi = "^2.0.19"
8383
uwsgitop = "^0.12"
8484
wagtail = "6.3"
8585
wagtail-metadata = "^5.0.0"
86+
djangorestframework-api-key = "^3.1.0"
8687

8788

8889
[tool.poetry.group.dev.dependencies]

users/models.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
from b2b.models import OrganizationPage
2020
from cms.constants import CMS_EDITORS_GROUP_NAME
21+
from openedx.constants import OPENEDX_USERNAME_MAX_LEN
2122
from openedx.models import OpenEdxUser
2223

2324
MALE = "m"
@@ -171,7 +172,9 @@ def _post_create_user(user, username):
171172
"""
172173
LegalAddress.objects.create(user=user)
173174
UserProfile.objects.create(user=user)
174-
OpenEdxUser.objects.create(user=user, edx_username=username)
175+
OpenEdxUser.objects.create(
176+
user=user, edx_username=username[:OPENEDX_USERNAME_MAX_LEN]
177+
)
175178

176179

177180
class UserManager(BaseUserManager):

0 commit comments

Comments
 (0)