mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-25 03:58:39 +02:00
Use audit log instead
This commit is contained in:
parent
3b378d3c81
commit
1c0f99eedc
6 changed files with 58 additions and 94 deletions
|
@ -1268,15 +1268,6 @@ class UserPortfolioPermissionAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
_meta = Meta()
|
_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
|
# Columns
|
||||||
list_display = [
|
list_display = [
|
||||||
"user",
|
"user",
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
import logging
|
|
||||||
from django.core.management import BaseCommand
|
|
||||||
from registrar.management.commands.utility.terminal_helper import PopulateScriptTemplate, TerminalColors, TerminalHelper
|
|
||||||
from registrar.models import UserPortfolioPermission, PortfolioInvitation
|
|
||||||
from auditlog.models import LogEntry
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand, PopulateScriptTemplate):
|
|
||||||
help = "Loops through each UserPortfolioPermission object and populates the invitation field"
|
|
||||||
|
|
||||||
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")
|
|
||||||
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()
|
|
||||||
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()
|
|
||||||
)
|
|
|
@ -1,25 +0,0 @@
|
||||||
# Generated by Django 4.2.10 on 2024-11-14 21:05
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("registrar", "0137_suborganization_city_suborganization_state_territory"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="userportfoliopermission",
|
|
||||||
name="invitation",
|
|
||||||
field=models.ForeignKey(
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
related_name="created_user_portfolio_permission",
|
|
||||||
to="registrar.portfolioinvitation",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -9,8 +9,6 @@ from registrar.models.user_portfolio_permission import UserPortfolioPermission
|
||||||
from .utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices # type: ignore
|
from .utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices # type: ignore
|
||||||
from .utility.time_stamped_model import TimeStampedModel
|
from .utility.time_stamped_model import TimeStampedModel
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.contrib.admin.models import LogEntry, ADDITION
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -103,7 +101,7 @@ class PortfolioInvitation(TimeStampedModel):
|
||||||
|
|
||||||
# and create a role for that user on this portfolio
|
# and create a role for that user on this portfolio
|
||||||
user_portfolio_permission, _ = UserPortfolioPermission.objects.get_or_create(
|
user_portfolio_permission, _ = UserPortfolioPermission.objects.get_or_create(
|
||||||
portfolio=self.portfolio, user=user, invitation=self
|
portfolio=self.portfolio, user=user
|
||||||
)
|
)
|
||||||
if self.roles and len(self.roles) > 0:
|
if self.roles and len(self.roles) > 0:
|
||||||
user_portfolio_permission.roles = self.roles
|
user_portfolio_permission.roles = self.roles
|
||||||
|
|
|
@ -65,16 +65,6 @@ class UserPortfolioPermission(TimeStampedModel):
|
||||||
help_text="Select one or more additional permissions.",
|
help_text="Select one or more additional permissions.",
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO - this needs a small script to update existing values
|
|
||||||
invitation = models.ForeignKey(
|
|
||||||
"registrar.PortfolioInvitation",
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
# We don't want to accidentally delete invitations
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
related_name="created_user_portfolio_permission",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
readable_roles = []
|
readable_roles = []
|
||||||
if self.roles:
|
if self.roles:
|
||||||
|
|
|
@ -243,6 +243,22 @@ class UserPortfolioPermissionModelAnnotation(BaseModelAnnotation):
|
||||||
# Get all members on this portfolio
|
# Get all members on this portfolio
|
||||||
return Q(portfolio=portfolio)
|
return Q(portfolio=portfolio)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_portfolio_invitation_id_query(cls):
|
||||||
|
"""Gets the id of the portfolio invitation that created this UserPortfolioPermission.
|
||||||
|
This makes the assumption that if an invitation is retrieved, it must have created the given
|
||||||
|
UserPortfolioPermission object."""
|
||||||
|
return Cast(
|
||||||
|
Subquery(
|
||||||
|
PortfolioInvitation.objects.filter(
|
||||||
|
status=PortfolioInvitation.PortfolioInvitationStatus.RETRIEVED,
|
||||||
|
email=OuterRef(OuterRef("user__email")),
|
||||||
|
portfolio=OuterRef(OuterRef("portfolio"))
|
||||||
|
).values("id")[:1]
|
||||||
|
),
|
||||||
|
output_field=TextField()
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_annotated_fields(cls, portfolio, csv_report=False):
|
def get_annotated_fields(cls, portfolio, csv_report=False):
|
||||||
"""
|
"""
|
||||||
|
@ -302,13 +318,11 @@ class UserPortfolioPermissionModelAnnotation(BaseModelAnnotation):
|
||||||
& Q(user__permissions__domain__domain_info__portfolio=portfolio),
|
& Q(user__permissions__domain__domain_info__portfolio=portfolio),
|
||||||
),
|
),
|
||||||
"source": Value("permission", output_field=CharField()),
|
"source": Value("permission", output_field=CharField()),
|
||||||
"invitation_date": Coalesce(
|
"invitation_date": PortfolioInvitationModelAnnotation.get_invitation_date_query(
|
||||||
Func(F("invitation__created_at"), Value("YYYY-MM-DD"), function="to_char", output_field=TextField()),
|
object_id_query=cls.get_portfolio_invitation_id_query()
|
||||||
Value("Invalid date"),
|
|
||||||
output_field=TextField(),
|
|
||||||
),
|
),
|
||||||
"invited_by": PortfolioInvitationModelAnnotation.get_invited_by_from_audit_log_query(
|
"invited_by": PortfolioInvitationModelAnnotation.get_invited_by_query(
|
||||||
object_id_query=Cast(OuterRef("invitation__id"), output_field=TextField())
|
object_id_query=cls.get_portfolio_invitation_id_query()
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,7 +364,39 @@ class PortfolioInvitationModelAnnotation(BaseModelAnnotation):
|
||||||
return Q(portfolio=portfolio)
|
return Q(portfolio=portfolio)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_invited_by_from_audit_log_query(cls, object_id_query):
|
def get_invitation_date_query(cls, object_id_query):
|
||||||
|
"""Returns the date at which the given invitation was created.
|
||||||
|
Grabs this data from the audit log, given that a portfolio invitation object
|
||||||
|
is specified via 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(
|
||||||
|
# Action time will always be equivalent to created_at in this context
|
||||||
|
display_date=Func(
|
||||||
|
F("action_time"),
|
||||||
|
Value("YYYY-MM-DD"),
|
||||||
|
function="to_char",
|
||||||
|
output_field=TextField()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.order_by("action_time")
|
||||||
|
.values("display_date")[:1]
|
||||||
|
),
|
||||||
|
Value("Invalid date"),
|
||||||
|
output_field=TextField()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_invited_by_query(cls, object_id_query):
|
||||||
|
"""Returns the user that created the given portfolio invitation.
|
||||||
|
Grabs this data from the audit log, given that a portfolio invitation object
|
||||||
|
is specified via object_id_query."""
|
||||||
return Coalesce(
|
return Coalesce(
|
||||||
Subquery(
|
Subquery(
|
||||||
LogEntry.objects.filter(
|
LogEntry.objects.filter(
|
||||||
|
@ -374,7 +420,7 @@ class PortfolioInvitationModelAnnotation(BaseModelAnnotation):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.order_by("action_time")
|
.order_by("action_time")
|
||||||
.values("display_email")
|
.values("display_email")[:1]
|
||||||
),
|
),
|
||||||
Value("Unknown"),
|
Value("Unknown"),
|
||||||
output_field=CharField(),
|
output_field=CharField(),
|
||||||
|
@ -415,15 +461,13 @@ class PortfolioInvitationModelAnnotation(BaseModelAnnotation):
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
"source": Value("invitation", output_field=CharField()),
|
"source": Value("invitation", output_field=CharField()),
|
||||||
"invitation_date": Coalesce(
|
"invitation_date": cls.get_invitation_date_query(
|
||||||
Func(F("created_at"), Value("YYYY-MM-DD"), function="to_char", output_field=TextField()),
|
object_id_query=Cast(OuterRef("id"), output_field=TextField())
|
||||||
Value("Invalid date"),
|
|
||||||
output_field=TextField(),
|
|
||||||
),
|
),
|
||||||
# TODO - replace this with a "creator" field on portfolio invitation. This should be another ticket.
|
# 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.
|
# 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")
|
# When that happens, just replace this with F("invitation__creator")
|
||||||
"invited_by": cls.get_invited_by_from_audit_log_query(
|
"invited_by": cls.get_invited_by_query(
|
||||||
object_id_query=Cast(OuterRef("id"), output_field=TextField())
|
object_id_query=Cast(OuterRef("id"), output_field=TextField())
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue