Merge conflict

This commit is contained in:
zandercymatics 2024-11-20 09:02:19 -07:00
parent 20c0813f78
commit 908e06c8eb
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
7 changed files with 20 additions and 21 deletions

View file

@ -36,7 +36,6 @@
</form> </form>
</section> </section>
</div> </div>
{% if member_count and member_count > 0 %}
<div class="section-outlined__utility-button mobile-lg:padding-right-105 {% if portfolio %} mobile:grid-col-12 desktop:grid-col-6 desktop:padding-left-3{% endif %}"> <div class="section-outlined__utility-button mobile-lg:padding-right-105 {% if portfolio %} mobile:grid-col-12 desktop:grid-col-6 desktop:padding-left-3{% endif %}">
<section aria-label="Domains report component" class="margin-top-205"> <section aria-label="Domains report component" class="margin-top-205">
<a href="{% url 'export_members_portfolio' %}" class="usa-button usa-button--unstyled usa-button--with-icon usa-button--justify-right" role="button"> <a href="{% url 'export_members_portfolio' %}" class="usa-button usa-button--unstyled usa-button--with-icon usa-button--justify-right" role="button">
@ -46,7 +45,6 @@
</a> </a>
</section> </section>
</div> </div>
{% endif %}
</div> </div>
<!-- ---------- MAIN TABLE ---------- --> <!-- ---------- MAIN TABLE ---------- -->

View file

@ -812,7 +812,7 @@ class MemberExportTest(MockDbForIndividualTests, MockEppLib):
@override_flag("organization_members", active=True) @override_flag("organization_members", active=True)
@less_console_noise_decorator @less_console_noise_decorator
def test_member_export(self): 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) content_type = ContentType.objects.get_for_model(PortfolioInvitation)
LogEntry.objects.create( LogEntry.objects.create(
user=self.lebowski_user, user=self.lebowski_user,

View file

@ -175,7 +175,7 @@ class MemberExport(BaseExport):
"additional_permissions_display", "additional_permissions_display",
"member_display", "member_display",
"domain_info", "domain_info",
"source", "type",
"invitation_date", "invitation_date",
"invited_by", "invited_by",
] ]

View file

@ -280,10 +280,12 @@ class UserPortfolioPermissionModelAnnotation(BaseModelAnnotation):
F("user__last_login"), Value("YYYY-MM-DD"), function="to_char", output_field=TextField() F("user__last_login"), Value("YYYY-MM-DD"), function="to_char", output_field=TextField()
) )
else: else:
# an array of domains, with id and name, colon separated
domain_query = Concat( domain_query = Concat(
F("user__permissions__domain_id"), F("user__permissions__domain_id"),
Value(":"), Value(":"),
F("user__permissions__domain__name"), F("user__permissions__domain__name"),
# specify the output_field to ensure union has same column types
output_field=CharField(), output_field=CharField(),
) )
last_active_query = Cast(F("user__last_login"), output_field=TextField()) last_active_query = Cast(F("user__last_login"), output_field=TextField())
@ -299,7 +301,9 @@ class UserPortfolioPermissionModelAnnotation(BaseModelAnnotation):
), ),
"additional_permissions_display": F("additional_permissions"), "additional_permissions_display": F("additional_permissions"),
"member_display": Case( "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")), 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( When(
Q(user__first_name__isnull=False) | Q(user__last_name__isnull=False), Q(user__first_name__isnull=False) | Q(user__last_name__isnull=False),
then=Concat( then=Concat(
@ -308,16 +312,18 @@ class UserPortfolioPermissionModelAnnotation(BaseModelAnnotation):
Coalesce(F("user__last_name"), Value("")), Coalesce(F("user__last_name"), Value("")),
), ),
), ),
# If neither, use an empty string
default=Value(""), default=Value(""),
output_field=CharField(), output_field=CharField(),
), ),
"domain_info": ArrayAgg( "domain_info": ArrayAgg(
domain_query, domain_query,
distinct=True, distinct=True,
# only include domains in portfolio
filter=Q(user__permissions__domain__isnull=False) filter=Q(user__permissions__domain__isnull=False)
& Q(user__permissions__domain__domain_info__portfolio=portfolio), & 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( "invitation_date": PortfolioInvitationModelAnnotation.get_invitation_date_query(
object_id_query=cls.get_portfolio_invitation_id_query() object_id_query=cls.get_portfolio_invitation_id_query()
), ),
@ -452,13 +458,14 @@ class PortfolioInvitationModelAnnotation(BaseModelAnnotation):
"last_active": Value("Invited", output_field=TextField()), "last_active": Value("Invited", output_field=TextField()),
"additional_permissions_display": F("additional_permissions"), "additional_permissions_display": F("additional_permissions"),
"member_display": F("email"), "member_display": F("email"),
# Use ArrayRemove to return an empty list when no domain invitations are found
"domain_info": ArrayRemoveNull( "domain_info": ArrayRemoveNull(
ArrayAgg( ArrayAgg(
Subquery(domain_invitations.values("domain_info")), Subquery(domain_invitations.values("domain_info")),
distinct=True, distinct=True,
) )
), ),
"source": Value("invitation", output_field=CharField()), "type": Value("invitedmember", output_field=CharField()),
"invitation_date": cls.get_invitation_date_query( "invitation_date": cls.get_invitation_date_query(
object_id_query=Cast(OuterRef("id"), output_field=TextField()) object_id_query=Cast(OuterRef("id"), output_field=TextField())
), ),

View file

@ -66,7 +66,7 @@ class PortfolioMembersJson(PortfolioMembersPermission, View):
"additional_permissions_display", "additional_permissions_display",
"member_display", "member_display",
"domain_info", "domain_info",
"source", "type",
) )
def initial_invitations_search(self, portfolio): def initial_invitations_search(self, portfolio):
@ -83,7 +83,7 @@ class PortfolioMembersJson(PortfolioMembersPermission, View):
"additional_permissions_display", "additional_permissions_display",
"member_display", "member_display",
"domain_info", "domain_info",
"source", "type",
) )
def apply_search_term(self, queryset, request): 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 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 []) 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 # Serialize member data
member_json = { member_json = {
"id": item.get("id", ""), "id": item.get("id", ""), # id is id of UserPortfolioPermission or PortfolioInvitation
"source": item.get("source", ""), "type": item.get("type", ""), # source is member or invitedmember
"name": " ".join(filter(None, [item.get("first_name", ""), item.get("last_name", "")])), "name": " ".join(filter(None, [item.get("first_name", ""), item.get("last_name", "")])),
"email": item.get("email_display", ""), "email": item.get("email_display", ""),
"member_display": item.get("member_display", ""), "member_display": item.get("member_display", ""),

View file

@ -461,14 +461,7 @@ class PortfolioMembersView(PortfolioMembersPermissionView, View):
def get(self, request): def get(self, request):
"""Add additional context data to the template.""" """Add additional context data to the template."""
# Get portfolio from session return render(request, "portfolio_members.html")
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)
class NewMemberView(PortfolioMembersPermissionView, FormMixin): class NewMemberView(PortfolioMembersPermissionView, FormMixin):

View file

@ -175,9 +175,10 @@ class ExportMembersPortfolio(View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
"""Returns the members report""" """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"): 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 = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = f'attachment; filename="members-for-{portfolio_display}.csv"' response["Content-Disposition"] = f'attachment; filename="members-for-{portfolio_display}.csv"'