This commit is contained in:
zandercymatics 2024-11-18 11:12:43 -07:00
parent 7075b72533
commit 3b378d3c81
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
5 changed files with 69 additions and 56 deletions

View file

@ -1268,6 +1268,15 @@ class UserPortfolioPermissionAdmin(ListHeaderAdmin):
_meta = Meta()
# Question for reviewers: should this include the invitation field?
# This is the same layout as before.
fieldsets = (
(
None,
{"fields": ("user", "portfolio", "invitation", "roles", "additional_permissions")},
),
)
# Columns
list_display = [
"user",
@ -1275,8 +1284,6 @@ class UserPortfolioPermissionAdmin(ListHeaderAdmin):
"get_roles",
]
readonly_fields = ["invitation"]
autocomplete_fields = ["user", "portfolio"]
search_fields = ["user__first_name", "user__last_name", "user__email", "portfolio__organization_name"]
search_help_text = "Search by first name, last name, email, or portfolio."

View file

@ -13,15 +13,22 @@ class Command(BaseCommand, PopulateScriptTemplate):
def handle(self, **kwargs):
"""Loops through each DomainRequest object and populates
its last_status_update and first_submitted_date values"""
self.existing_invitations = PortfolioInvitation.objects.filter(portfolio__isnull=False, email__isnull=False).select_related('portfolio')
self.existing_invitations = PortfolioInvitation.objects.filter(
portfolio__isnull=False, email__isnull=False
).select_related("portfolio")
filter_condition = {"invitation__isnull": True, "portfolio__isnull": False, "user__email__isnull": False}
self.mass_update_records(UserPortfolioPermission, filter_condition, fields_to_update=["invitation"])
def update_record(self, record: UserPortfolioPermission):
"""Associate the invitation to the right object"""
record.invitation = self.existing_invitations.filter(email=record.user.email, portfolio=record.portfolio).first()
record.invitation = self.existing_invitations.filter(
email=record.user.email, portfolio=record.portfolio
).first()
TerminalHelper.colorful_logger("INFO", "OKCYAN", f"{TerminalColors.OKCYAN}Adding invitation to {record}")
def should_skip_record(self, record) -> bool:
"""There is nothing to add if no invitation exists"""
return not record or not self.existing_invitations.filter(email=record.user.email, portfolio=record.portfolio).exists()
return (
not record
or not self.existing_invitations.filter(email=record.user.email, portfolio=record.portfolio).exists()
)

View file

@ -39,8 +39,10 @@ from django.db.models import (
Func,
Case,
When,
Exists,
)
from django.db.models.functions import Concat, Coalesce, Cast
from registrar.models.user_group import UserGroup
from registrar.models.user_portfolio_permission import UserPortfolioPermission
from registrar.models.utility.generic_helper import convert_queryset_to_dict
from registrar.models.utility.orm_helper import ArrayRemove
@ -305,23 +307,8 @@ class UserPortfolioPermissionModelAnnotation(BaseModelAnnotation):
Value("Invalid date"),
output_field=TextField(),
),
# TODO - replace this with a "creator" field on portfolio invitation. This should be another ticket.
# Grab the invitation creator from the audit log. This will need to be replaced with a creator field.
# When that happens, just replace this with F("invitation__creator")
"invited_by": Coalesce(
Subquery(
LogEntry.objects.filter(
content_type=ContentType.objects.get_for_model(PortfolioInvitation),
object_id=Cast(
OuterRef("invitation__id"), output_field=TextField()
), # Look up the invitation's ID
action_flag=ADDITION,
)
.order_by("action_time")
.values("user__email")[:1]
),
Value("Unknown"),
output_field=CharField(),
"invited_by": PortfolioInvitationModelAnnotation.get_invited_by_from_audit_log_query(
object_id_query=Cast(OuterRef("invitation__id"), output_field=TextField())
),
}
@ -362,6 +349,37 @@ class PortfolioInvitationModelAnnotation(BaseModelAnnotation):
# Get all members on this portfolio
return Q(portfolio=portfolio)
@classmethod
def get_invited_by_from_audit_log_query(cls, object_id_query):
return Coalesce(
Subquery(
LogEntry.objects.filter(
content_type=ContentType.objects.get_for_model(PortfolioInvitation),
object_id=object_id_query,
action_flag=ADDITION,
)
.annotate(
display_email=Case(
When(
Exists(
UserGroup.objects.filter(
name__in=["cisa_analysts_group", "full_access_group"],
user=OuterRef("user"),
)
),
then=Value("help@get.gov")
),
default=F("user__email"),
output_field=CharField()
)
)
.order_by("action_time")
.values("display_email")
),
Value("Unknown"),
output_field=CharField(),
)
@classmethod
def get_annotated_fields(cls, portfolio, csv_report=False):
"""
@ -383,7 +401,6 @@ class PortfolioInvitationModelAnnotation(BaseModelAnnotation):
email=OuterRef("email"), # Check if email matches the OuterRef("email")
domain__domain_info__portfolio=portfolio, # Check if the domain's portfolio matches the given portfolio
).annotate(domain_info=domain_query)
return {
"first_name": Value(None, output_field=CharField()),
"last_name": Value(None, output_field=CharField()),
@ -406,19 +423,8 @@ class PortfolioInvitationModelAnnotation(BaseModelAnnotation):
# TODO - replace this with a "creator" field on portfolio invitation. This should be another ticket.
# Grab the invitation creator from the audit log. This will need to be replaced with a creator field.
# When that happens, just replace this with F("invitation__creator")
"invited_by": Coalesce(
Subquery(
LogEntry.objects.filter(
content_type=ContentType.objects.get_for_model(PortfolioInvitation),
# Look up the invitation's ID. LogEntry expects a string as this it is stored as json.
object_id=Cast(OuterRef("id"), output_field=TextField()),
action_flag=ADDITION,
)
.order_by("action_time")
.values("user__email")[:1]
),
Value("Unknown"),
output_field=CharField(),
"invited_by": cls.get_invited_by_from_audit_log_query(
object_id_query=Cast(OuterRef("id"), output_field=TextField())
),
}

View file

@ -1,9 +1,6 @@
from django.http import JsonResponse
from django.core.paginator import Paginator
from django.db.models import Value, F, CharField, TextField, Q, Case, When, OuterRef, Subquery
from django.db.models.expressions import Func
from django.db.models.functions import Cast, Coalesce, Concat
from django.contrib.postgres.aggregates import ArrayAgg
from django.db.models import F, Q
from django.urls import reverse
from django.views import View

View file

@ -385,22 +385,18 @@ class PortfolioMembersView(PortfolioMembersPermissionView, View):
def get(self, request):
"""Add additional context data to the template."""
return render(request, "portfolio_members.html")
def get_context_data(self, **kwargs):
"""Add additional context data to the template."""
context = super().get_context_data(**kwargs)
portfolio = self.request.session.get("portfolio")
user_count = portfolio.portfolio_users.count()
invitation_count = PortfolioInvitation.objects.filter(
portfolio=portfolio
).count()
context["member_count"] = user_count + invitation_count
# check if any portfolio invitations exist 4 portfolio
# check if any userportfolioroles exist 4 portfolio
return context
# 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)
class NewMemberView(PortfolioMembersPermissionView, FormMixin):