Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions arches/management/commands/bulk_approve.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from django.contrib.auth import get_user_model
from django.core.management.base import BaseCommand, CommandError

from arches.app.utils.bulkupload import (
user_has_provisional_edits,
approve_all_provisional_edits_for_user,
)


class Command(BaseCommand):
"""
Approves all provisional edits for specified users.

This command can process users by either user IDs or usernames, with comprehensive
validation and graceful handling of non-existent users.

Arguments:
--user_ids: One or more user IDs to approve edits for (separate by space)
--user_names: One or more usernames to approve edits for (separate by space)

Examples:
python manage.py bulk_approve --user_ids 1 2 3
python manage.py bulk_approve --user_names john_doe jane_smith admin
"""

def add_arguments(self, parser):
Comment thread
razekmh marked this conversation as resolved.
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
"-u",
"--user_ids",
type=int,
nargs="+",
metavar="USER_ID",
default=[],
help="One or more user IDs to approve edits for (separate by space)",
)
group.add_argument(
"-n",
"--user_names",
type=str,
nargs="+",
metavar="USER_NAME",
default=[],
help="One or more user names to approve edits for (separate by space)",
)

def handle(self, *args, **options):
user_ids = options.get("user_ids")
user_names = options.get("user_names")

Comment thread
razekmh marked this conversation as resolved.
User = get_user_model()
if user_ids:
existing_users = User.objects.filter(pk__in=user_ids).values_list(
"id", flat=True
)
if not existing_users:
raise CommandError(f"User(s) with ID(s) {user_ids} do(es) not exist.")
missing_user_ids = set(user_ids) - set(existing_users)
if missing_user_ids:
self.stdout.write(
self.style.WARNING(
f"User(s) with ID(s) {missing_user_ids} do(es) not exist and will be skipped."
)
)
user_ids = list(existing_users)

if user_names:
existing_user_names_and_ids = User.objects.filter(
username__in=user_names
).values("id", "username")
if not existing_user_names_and_ids:
raise CommandError(
f"User(s) with name(s) {user_names} do(es) not exist."
)
found_usernames = {user["username"] for user in existing_user_names_and_ids}
missing_usernames = set(user_names) - found_usernames

if missing_usernames:
self.stdout.write(
self.style.WARNING(
f"User(s) with name(s) {missing_usernames} do(es) not exist and will be skipped."
)
)
user_ids = [user["id"] for user in existing_user_names_and_ids]

for user_id in user_ids:
Comment thread
razekmh marked this conversation as resolved.
if not user_has_provisional_edits(user_id):
self.stdout.write(
self.style.WARNING(
f"No provisional edits found for user ID {user_id} or username {User.objects.filter(pk=user_id).first().username}"
Copy link

Copilot AI Aug 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line performs an unnecessary database query inside a loop. Consider fetching user information once and storing it in a dictionary to avoid repeated queries.

Suggested change
f"No provisional edits found for user ID {user_id} or username {User.objects.filter(pk=user_id).first().username}"
# Fetch all usernames for the user_ids in a single query
user_id_to_username = dict(User.objects.filter(pk__in=user_ids).values_list("id", "username"))
for user_id in user_ids:
username = user_id_to_username.get(user_id, "<unknown>")
if not user_has_provisional_edits(user_id):
self.stdout.write(
self.style.WARNING(
f"No provisional edits found for user ID {user_id} or username {username}"

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Aug 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .first().username call could raise an AttributeError if the user doesn't exist. Although user_ids are validated earlier, this could still fail if a user is deleted between validation and processing.

Suggested change
f"No provisional edits found for user ID {user_id} or username {User.objects.filter(pk=user_id).first().username}"
f"No provisional edits found for user ID {user_id} or username {User.objects.filter(pk=user_id).first().username if User.objects.filter(pk=user_id).first() else '<deleted user>'}"

Copilot uses AI. Check for mistakes.
)
)
continue

approve_all_provisional_edits_for_user(user_id)
Comment thread
razekmh marked this conversation as resolved.
Copy link

Copilot AI Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The command should handle potential exceptions from approve_all_provisional_edits_for_user() to prevent the command from crashing and provide meaningful error messages to users.

Suggested change
approve_all_provisional_edits_for_user(user_id)
try:
approve_all_provisional_edits_for_user(user_id)
except Exception as e:
self.stdout.write(
self.style.ERROR(
f"Failed to approve provisional edits for user ID {user_id}: {e}"
)
)
continue

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the idea but I do not think Arches has a standard way to show the errors.

self.stdout.write(
self.style.SUCCESS(
f"All provisional edits for user ID {user_id} or username {User.objects.filter(pk=user_id).first().username} have been approved."
Copy link

Copilot AI Aug 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line performs another unnecessary database query inside a loop. The same user information should be cached to avoid repeated database hits.

Suggested change
f"All provisional edits for user ID {user_id} or username {User.objects.filter(pk=user_id).first().username} have been approved."
f"All provisional edits for user ID {user_id} or username {username} have been approved."

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Aug 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .first().username call could raise an AttributeError if the user doesn't exist. Although user_ids are validated earlier, this could still fail if a user is deleted between validation and processing.

Copilot uses AI. Check for mistakes.
)
)