From 020839b6af413fc366c7859be67a64ec03a22978 Mon Sep 17 00:00:00 2001 From: CP Date: Wed, 28 May 2025 08:39:28 -0400 Subject: [PATCH 1/5] hold --- users/adapters.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/users/adapters.py b/users/adapters.py index ce2daea980..e5d00d0f43 100644 --- a/users/adapters.py +++ b/users/adapters.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING +from b2b.models import ContractPage from mitol.scim.adapters import UserAdapter from openedx.models import OpenEdxUser @@ -25,6 +26,7 @@ class LearnUserAdapter(UserAdapter): user_profile: UserProfile legal_address: LegalAddress openedx_user: OpenEdxUser + b2b_contracts: ContractPage def __init__(self, obj, request=None): super().__init__(obj, request=request) @@ -37,6 +39,10 @@ def __init__(self, obj, request=None): self.obj, "legal_address", LegalAddress() ) + self.b2b_contracts = self.obj.b2b_contracts = getattr( + self.obj, "b2b_contracts", ContractPage() + ) + self.openedx_user = self.obj.openedx_user if self.openedx_user is None: del self.obj.openedx_user @@ -54,24 +60,21 @@ def from_dict(self, d): Consume a ``dict`` conforming to the SCIM User Schema, updating the internal user object with data from the ``dict``. - Please note, the user object is not saved within this method. To - persist the changes made by this method, please call ``.save()`` on the - adapter. Eg:: - - scim_user.from_dict(d) - scim_user.save() + Note: This method does NOT save the user object. To persist changes, + call ``.save()`` on the adapter. """ super().from_dict(d) self.obj.name = d.get("fullName", "") - first_name = d.get("name", {}).get("given_name", "") - if first_name: - self.legal_address.first_name = first_name + name_data = d.get("name", {}) + self.legal_address.first_name = name_data.get("given_name", "") or self.legal_address.first_name + self.legal_address.last_name = name_data.get("last_name", "") or self.legal_address.last_name - last_name = d.get("name", {}).get("last_name", "") - if last_name: - self.legal_address.last_name = last_name + organization_name = d.get("organization") + if organization_name: + contract_pages = ContractPage.objects.filter(organization__name=organization_name) + self.obj.b2b_contracts.add(*contract_pages) def _save_related(self): self.user_profile.user = self.obj From 83bc32e596b38fa3850d577d3f724bb67dd74e49 Mon Sep 17 00:00:00 2001 From: CP Date: Wed, 28 May 2025 12:41:38 -0400 Subject: [PATCH 2/5] Add tests --- users/adapters.py | 17 ++++----- users/adapters_test.py | 79 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 users/adapters_test.py diff --git a/users/adapters.py b/users/adapters.py index e5d00d0f43..74001b1946 100644 --- a/users/adapters.py +++ b/users/adapters.py @@ -35,13 +35,12 @@ def __init__(self, obj, request=None): self.obj, "user_profile", UserProfile() ) - self.legal_address = self.obj.legal_address = getattr( - self.obj, "legal_address", LegalAddress() - ) + try: + self.legal_address = self.obj.legal_address # triggers DB fetch if needed + except LegalAddress.DoesNotExist: + self.legal_address = LegalAddress() - self.b2b_contracts = self.obj.b2b_contracts = getattr( - self.obj, "b2b_contracts", ContractPage() - ) + self.b2b_contracts = self.obj.b2b_contracts self.openedx_user = self.obj.openedx_user if self.openedx_user is None: @@ -68,8 +67,10 @@ def from_dict(self, d): self.obj.name = d.get("fullName", "") name_data = d.get("name", {}) - self.legal_address.first_name = name_data.get("given_name", "") or self.legal_address.first_name - self.legal_address.last_name = name_data.get("last_name", "") or self.legal_address.last_name + + self.legal_address.first_name = name_data.get("given_name") or self.legal_address.first_name + self.legal_address.last_name = name_data.get("last_name") or self.legal_address.last_name + organization_name = d.get("organization") if organization_name: diff --git a/users/adapters_test.py b/users/adapters_test.py new file mode 100644 index 0000000000..3e0aed55ce --- /dev/null +++ b/users/adapters_test.py @@ -0,0 +1,79 @@ +import pytest +from unittest import mock + +from b2b.factories import ContractPageFactory +from users.adapters import LearnUserAdapter +from users.factories import UserFactory +from users.models import UserProfile, LegalAddress +from openedx.models import OpenEdxUser +from b2b.models import ContractPage + +@pytest.mark.django_db +def test_init_sets_related_objects(): + user = UserFactory() + adapter = LearnUserAdapter(user) + + assert isinstance(adapter.user_profile, UserProfile) + assert isinstance(adapter.legal_address, LegalAddress) + assert isinstance(adapter.openedx_user, OpenEdxUser) + +@pytest.mark.django_db +def test_display_name_returns_name(): + user = UserFactory(name="John Doe") + adapter = LearnUserAdapter(user) + + assert adapter.display_name == "John Doe" + +@pytest.mark.django_db +def test_from_dict_updates_user_and_related(): + user = UserFactory.create(name="Old Name") + user.legal_address.first_name = "OldFirst" + user.legal_address.last_name = "OldLast" + user.legal_address.save() + adapter = LearnUserAdapter(user) + + contract_page = ContractPageFactory.create(organization__name="Acme Corp") + data = { + "fullName": "New Name", + "name": {"given_name": "NewFirst", "last_name": "NewLast"}, + "organization": "Acme Corp" + } + + adapter.from_dict(data) + adapter._save_related() + adapter.legal_address.refresh_from_db() + assert adapter.obj.name == "New Name" + assert adapter.legal_address.first_name == "NewFirst" + assert adapter.legal_address.last_name == "NewLast" + assert user.b2b_contracts.filter(id=contract_page.id).exists() + +@pytest.mark.django_db +def test_from_dict_keeps_existing_names_if_missing(): + user = UserFactory.create(name="Old Name") + user.legal_address.first_name = "OldFirst" + user.legal_address.last_name = "OldLast" + user.legal_address.save() + adapter = LearnUserAdapter(user) + + data = {"fullName": "Another Name", "name": {}} + adapter.from_dict(data) + + adapter.legal_address.refresh_from_db() + + assert adapter.legal_address.first_name == "OldFirst" + assert adapter.legal_address.last_name == "OldLast" + +@pytest.mark.django_db +def test_save_related_saves_all(): + user = UserFactory() + adapter = LearnUserAdapter(user) + + adapter.user_profile = mock.MagicMock() + adapter.legal_address = mock.MagicMock() + adapter.openedx_user = mock.MagicMock() + + adapter._save_related() + + adapter.user_profile.save.assert_called_once() + adapter.legal_address.save.assert_called_once() + adapter.openedx_user.save.assert_called_once() From 55b7cf42664dcc9bda6e444515b4dc4b8bd73746 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 29 May 2025 13:53:52 +0000 Subject: [PATCH 3/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- users/adapters.py | 15 ++++++++++----- users/adapters_test.py | 17 +++++++++++------ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/users/adapters.py b/users/adapters.py index 74001b1946..960662787a 100644 --- a/users/adapters.py +++ b/users/adapters.py @@ -1,8 +1,8 @@ from typing import TYPE_CHECKING -from b2b.models import ContractPage from mitol.scim.adapters import UserAdapter +from b2b.models import ContractPage from openedx.models import OpenEdxUser from users.models import LegalAddress, UserProfile @@ -68,13 +68,18 @@ def from_dict(self, d): name_data = d.get("name", {}) - self.legal_address.first_name = name_data.get("given_name") or self.legal_address.first_name - self.legal_address.last_name = name_data.get("last_name") or self.legal_address.last_name - + self.legal_address.first_name = ( + name_data.get("given_name") or self.legal_address.first_name + ) + self.legal_address.last_name = ( + name_data.get("last_name") or self.legal_address.last_name + ) organization_name = d.get("organization") if organization_name: - contract_pages = ContractPage.objects.filter(organization__name=organization_name) + contract_pages = ContractPage.objects.filter( + organization__name=organization_name + ) self.obj.b2b_contracts.add(*contract_pages) def _save_related(self): diff --git a/users/adapters_test.py b/users/adapters_test.py index 3e0aed55ce..bea9386c0e 100644 --- a/users/adapters_test.py +++ b/users/adapters_test.py @@ -1,12 +1,13 @@ -import pytest from unittest import mock +import pytest + from b2b.factories import ContractPageFactory +from openedx.models import OpenEdxUser from users.adapters import LearnUserAdapter from users.factories import UserFactory -from users.models import UserProfile, LegalAddress -from openedx.models import OpenEdxUser -from b2b.models import ContractPage +from users.models import LegalAddress, UserProfile + @pytest.mark.django_db def test_init_sets_related_objects(): @@ -17,6 +18,7 @@ def test_init_sets_related_objects(): assert isinstance(adapter.legal_address, LegalAddress) assert isinstance(adapter.openedx_user, OpenEdxUser) + @pytest.mark.django_db def test_display_name_returns_name(): user = UserFactory(name="John Doe") @@ -24,6 +26,7 @@ def test_display_name_returns_name(): assert adapter.display_name == "John Doe" + @pytest.mark.django_db def test_from_dict_updates_user_and_related(): user = UserFactory.create(name="Old Name") @@ -36,7 +39,7 @@ def test_from_dict_updates_user_and_related(): data = { "fullName": "New Name", "name": {"given_name": "NewFirst", "last_name": "NewLast"}, - "organization": "Acme Corp" + "organization": "Acme Corp", } adapter.from_dict(data) @@ -47,6 +50,7 @@ def test_from_dict_updates_user_and_related(): assert adapter.legal_address.last_name == "NewLast" assert user.b2b_contracts.filter(id=contract_page.id).exists() + @pytest.mark.django_db def test_from_dict_keeps_existing_names_if_missing(): user = UserFactory.create(name="Old Name") @@ -57,12 +61,13 @@ def test_from_dict_keeps_existing_names_if_missing(): data = {"fullName": "Another Name", "name": {}} adapter.from_dict(data) - + adapter.legal_address.refresh_from_db() assert adapter.legal_address.first_name == "OldFirst" assert adapter.legal_address.last_name == "OldLast" + @pytest.mark.django_db def test_save_related_saves_all(): user = UserFactory() From 8ea027a319021ffffff539738af380c93fa3d66e Mon Sep 17 00:00:00 2001 From: CP Date: Thu, 29 May 2025 11:22:21 -0400 Subject: [PATCH 4/5] ruff --- users/adapters_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/users/adapters_test.py b/users/adapters_test.py index 3e0aed55ce..437602c467 100644 --- a/users/adapters_test.py +++ b/users/adapters_test.py @@ -40,7 +40,7 @@ def test_from_dict_updates_user_and_related(): } adapter.from_dict(data) - adapter._save_related() + adapter._save_related() # noqa: SLF001 adapter.legal_address.refresh_from_db() assert adapter.obj.name == "New Name" assert adapter.legal_address.first_name == "NewFirst" @@ -72,7 +72,7 @@ def test_save_related_saves_all(): adapter.legal_address = mock.MagicMock() adapter.openedx_user = mock.MagicMock() - adapter._save_related() + adapter._save_related() # noqa: SLF001 adapter.user_profile.save.assert_called_once() adapter.legal_address.save.assert_called_once() From c3555f8a3d0f3bce70d5580c1a3cbb81997878a3 Mon Sep 17 00:00:00 2001 From: CP Date: Mon, 2 Jun 2025 11:54:41 -0400 Subject: [PATCH 5/5] hold --- users/adapters.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/users/adapters.py b/users/adapters.py index 960662787a..8f0f5db86d 100644 --- a/users/adapters.py +++ b/users/adapters.py @@ -80,7 +80,7 @@ def from_dict(self, d): contract_pages = ContractPage.objects.filter( organization__name=organization_name ) - self.obj.b2b_contracts.add(*contract_pages) + self.b2b_contracts.add(*contract_pages) def _save_related(self): self.user_profile.user = self.obj @@ -91,3 +91,5 @@ def _save_related(self): self.openedx_user.user = self.obj self.openedx_user.save() + + self.obj.b2b_contracts.add(*self.b2b_contracts)