From e1347de0a402b676b5da419d40eaeaf1547aa75e Mon Sep 17 00:00:00 2001 From: Ryan Tanenholz <44756861+ryantanen@users.noreply.github.com> Date: Sun, 14 Sep 2025 15:18:37 -0400 Subject: [PATCH] Penn Groups get all groups --- backend/Platform/settings/base.py | 3 +++ backend/Platform/urls.py | 1 + backend/accounts/models.py | 26 ++++++++++++++++---------- backend/penngroups/__init__.py | 0 backend/penngroups/apps.py | 5 +++++ backend/penngroups/urls.py | 9 +++++++++ backend/penngroups/views.py | 18 ++++++++++++++++++ 7 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 backend/penngroups/__init__.py create mode 100644 backend/penngroups/apps.py create mode 100644 backend/penngroups/urls.py create mode 100644 backend/penngroups/views.py diff --git a/backend/Platform/settings/base.py b/backend/Platform/settings/base.py index 2b0cb048..f2f5448d 100644 --- a/backend/Platform/settings/base.py +++ b/backend/Platform/settings/base.py @@ -242,3 +242,6 @@ # Media Upload Settings MEDIA_ROOT = os.path.join(BASE_DIR, "accounts", "mediafiles") MEDIA_URL = "/media/" + +PENN_GROUPS_USER = os.environ.get("PENN_GROUPS_USER", "") +PENN_GROUPS_PWD = os.environ.get("PENN_GROUPS_PWD", "") diff --git a/backend/Platform/urls.py b/backend/Platform/urls.py index b93269b4..e3548353 100644 --- a/backend/Platform/urls.py +++ b/backend/Platform/urls.py @@ -14,6 +14,7 @@ path("options/", include("options.urls", namespace="options")), path("identity/", include("identity.urls", namespace="identity")), path("healthcheck/", include("health.urls", namespace="healthcheck")), + path("penngroups/", include("penngroups.urls", namespace="penngroups")), path("s/", include("shortener.urls", namespace="shortener")), path( "openapi/", diff --git a/backend/accounts/models.py b/backend/accounts/models.py index edcaceb8..318ce3cb 100644 --- a/backend/accounts/models.py +++ b/backend/accounts/models.py @@ -108,7 +108,10 @@ def ensure_student_object(sender, instance, created, **kwargs): This post_save hook triggers automatically when a User object is saved, and if no Student object exists for that User, it will create one """ - Student.objects.get_or_create(user=instance) + # Only create Student object if the User instance has been saved to the database + # (has a primary key) to avoid "Model instances passed to related filters must be saved" error + if instance.pk: + Student.objects.get_or_create(user=instance) class Email(models.Model): @@ -180,12 +183,15 @@ def load_privacy_settings(sender, instance, created, **kwargs): privacy settings for the User """ - # In most cases, first checking if settings exists should reduce the number of queries - # to the database - if not instance.privacy_setting.exists(): - resources = PrivacyResource.objects.all() - settings = [ - PrivacySetting(user=instance, resource=resource, enabled=True) - for resource in resources - ] - PrivacySetting.objects.bulk_create(settings, ignore_conflicts=True) + # Only access relationships if the User instance has been saved to the database + # (has a primary key) to avoid "User instance needs to have a primary key value" error + if instance.pk: + # In most cases, first checking if settings exists should reduce the number of queries + # to the database + if not instance.privacy_setting.exists(): + resources = PrivacyResource.objects.all() + settings = [ + PrivacySetting(user=instance, resource=resource, enabled=True) + for resource in resources + ] + PrivacySetting.objects.bulk_create(settings, ignore_conflicts=True) diff --git a/backend/penngroups/__init__.py b/backend/penngroups/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/penngroups/apps.py b/backend/penngroups/apps.py new file mode 100644 index 00000000..525b37a9 --- /dev/null +++ b/backend/penngroups/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class PennGroupsConfig(AppConfig): + name = "penngroups" diff --git a/backend/penngroups/urls.py b/backend/penngroups/urls.py new file mode 100644 index 00000000..cff3504e --- /dev/null +++ b/backend/penngroups/urls.py @@ -0,0 +1,9 @@ +from django.urls import path +from penngroups.views import PennGroupsGetGroupsView + + +app_name = "penngroups" + +urlpatterns = [ + path("groups/", PennGroupsGetGroupsView.as_view(), name="agh"), +] diff --git a/backend/penngroups/views.py b/backend/penngroups/views.py new file mode 100644 index 00000000..12676f26 --- /dev/null +++ b/backend/penngroups/views.py @@ -0,0 +1,18 @@ +import requests +from http import HTTPStatus +from django.http import JsonResponse +from django.views.generic import View + +from Platform.settings.base import PENN_GROUPS_PWD, PENN_GROUPS_USER + + +class PennGroupsGetGroupsView(View): + def get(self, request): + if not request.user.is_authenticated: + return JsonResponse({"message": "Unauthorized"}, status=HTTPStatus.UNAUTHORIZED) + if request.user.pennid is None: + return JsonResponse({"message": "Unknown user"}, status=HTTPStatus.NOT_FOUND) + + url = f"https://grouperWs.apps.upenn.edu/grouperWs/servicesRest/4.9.3/subjects/{request.user.pennid}/groups" + response = requests.get(url, auth=(PENN_GROUPS_USER, PENN_GROUPS_PWD)) + return JsonResponse(response.json(), status=HTTPStatus.OK) \ No newline at end of file