diff --git a/src/registrar/templates/includes/members_table.html b/src/registrar/templates/includes/members_table.html index 411018fb4..6733c8d95 100644 --- a/src/registrar/templates/includes/members_table.html +++ b/src/registrar/templates/includes/members_table.html @@ -36,7 +36,6 @@ - {% if member_count and member_count > 0 %}
@@ -46,7 +45,6 @@
- {% endif %} diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index 664a038f2..c1a11d85c 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -812,7 +812,7 @@ class MemberExportTest(MockDbForIndividualTests, MockEppLib): @override_flag("organization_members", active=True) @less_console_noise_decorator def test_member_export(self): - """Tests the member export report""" + """Tests the member export report by comparing the csv output.""" content_type = ContentType.objects.get_for_model(PortfolioInvitation) LogEntry.objects.create( user=self.lebowski_user, diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py index 537d61538..ab806f81a 100644 --- a/src/registrar/utility/csv_export.py +++ b/src/registrar/utility/csv_export.py @@ -175,7 +175,7 @@ class MemberExport(BaseExport): "additional_permissions_display", "member_display", "domain_info", - "source", + "type", "invitation_date", "invited_by", ] diff --git a/src/registrar/utility/model_annotations.py b/src/registrar/utility/model_annotations.py index 6fff51c2c..453653b52 100644 --- a/src/registrar/utility/model_annotations.py +++ b/src/registrar/utility/model_annotations.py @@ -280,10 +280,12 @@ class UserPortfolioPermissionModelAnnotation(BaseModelAnnotation): F("user__last_login"), Value("YYYY-MM-DD"), function="to_char", output_field=TextField() ) else: + # an array of domains, with id and name, colon separated domain_query = Concat( F("user__permissions__domain_id"), Value(":"), F("user__permissions__domain__name"), + # specify the output_field to ensure union has same column types output_field=CharField(), ) last_active_query = Cast(F("user__last_login"), output_field=TextField()) @@ -299,7 +301,9 @@ class UserPortfolioPermissionModelAnnotation(BaseModelAnnotation): ), "additional_permissions_display": F("additional_permissions"), "member_display": Case( + # If email is present and not blank, use email When(Q(user__email__isnull=False) & ~Q(user__email=""), then=F("user__email")), + # If first name or last name is present, use concatenation of first_name + " " + last_name When( Q(user__first_name__isnull=False) | Q(user__last_name__isnull=False), then=Concat( @@ -308,16 +312,18 @@ class UserPortfolioPermissionModelAnnotation(BaseModelAnnotation): Coalesce(F("user__last_name"), Value("")), ), ), + # If neither, use an empty string default=Value(""), output_field=CharField(), ), "domain_info": ArrayAgg( domain_query, distinct=True, + # only include domains in portfolio filter=Q(user__permissions__domain__isnull=False) & Q(user__permissions__domain__domain_info__portfolio=portfolio), ), - "source": Value("permission", output_field=CharField()), + "type": Value("member", output_field=CharField()), "invitation_date": PortfolioInvitationModelAnnotation.get_invitation_date_query( object_id_query=cls.get_portfolio_invitation_id_query() ), @@ -452,13 +458,14 @@ class PortfolioInvitationModelAnnotation(BaseModelAnnotation): "last_active": Value("Invited", output_field=TextField()), "additional_permissions_display": F("additional_permissions"), "member_display": F("email"), + # Use ArrayRemove to return an empty list when no domain invitations are found "domain_info": ArrayRemoveNull( ArrayAgg( Subquery(domain_invitations.values("domain_info")), distinct=True, ) ), - "source": Value("invitation", output_field=CharField()), + "type": Value("invitedmember", output_field=CharField()), "invitation_date": cls.get_invitation_date_query( object_id_query=Cast(OuterRef("id"), output_field=TextField()) ), diff --git a/src/registrar/views/portfolio_members_json.py b/src/registrar/views/portfolio_members_json.py index 3bf761858..bf89dcd82 100644 --- a/src/registrar/views/portfolio_members_json.py +++ b/src/registrar/views/portfolio_members_json.py @@ -66,7 +66,7 @@ class PortfolioMembersJson(PortfolioMembersPermission, View): "additional_permissions_display", "member_display", "domain_info", - "source", + "type", ) def initial_invitations_search(self, portfolio): @@ -83,7 +83,7 @@ class PortfolioMembersJson(PortfolioMembersPermission, View): "additional_permissions_display", "member_display", "domain_info", - "source", + "type", ) def apply_search_term(self, queryset, request): @@ -119,12 +119,12 @@ class PortfolioMembersJson(PortfolioMembersPermission, View): view_only = not user.has_edit_members_portfolio_permission(portfolio) or not user_can_edit_other_users is_admin = UserPortfolioRoleChoices.ORGANIZATION_ADMIN in (item.get("roles") or []) - action_url = reverse("member" if item["source"] == "permission" else "invitedmember", kwargs={"pk": item["id"]}) + action_url = reverse(item["type"], kwargs={"pk": item["id"]}) # Serialize member data member_json = { - "id": item.get("id", ""), - "source": item.get("source", ""), + "id": item.get("id", ""), # id is id of UserPortfolioPermission or PortfolioInvitation + "type": item.get("type", ""), # source is member or invitedmember "name": " ".join(filter(None, [item.get("first_name", ""), item.get("last_name", "")])), "email": item.get("email_display", ""), "member_display": item.get("member_display", ""), diff --git a/src/registrar/views/portfolios.py b/src/registrar/views/portfolios.py index d35ae20dd..373d1619d 100644 --- a/src/registrar/views/portfolios.py +++ b/src/registrar/views/portfolios.py @@ -461,14 +461,7 @@ class PortfolioMembersView(PortfolioMembersPermissionView, View): def get(self, request): """Add additional context data to the template.""" - # Get portfolio from session - portfolio = request.session.get("portfolio") - context = {} - if portfolio: - user_count = portfolio.portfolio_users.count() - invitation_count = PortfolioInvitation.objects.filter(portfolio=portfolio).count() - context.update({"member_count": user_count + invitation_count}) - return render(request, "portfolio_members.html", context=context) + return render(request, "portfolio_members.html") class NewMemberView(PortfolioMembersPermissionView, FormMixin): diff --git a/src/registrar/views/report_views.py b/src/registrar/views/report_views.py index 880de5d79..cff177d6d 100644 --- a/src/registrar/views/report_views.py +++ b/src/registrar/views/report_views.py @@ -175,9 +175,10 @@ class ExportMembersPortfolio(View): def get(self, request, *args, **kwargs): """Returns the members report""" - portfolio_display = "portfolio" + # Swap the spaces for dashes to make the formatted name look prettier + portfolio_display = "organization" if request.session.get("portfolio"): - portfolio_display = str(request.session.get("portfolio")).replace(" ", "-") + portfolio_display = str(request.session.get("portfolio")).lower().replace(" ", "-") response = HttpResponse(content_type="text/csv") response["Content-Disposition"] = f'attachment; filename="members-for-{portfolio_display}.csv"'