From d6495b410fc218c9caf2af1fd8cc9d94447599c3 Mon Sep 17 00:00:00 2001 From: Longze Chen Date: Wed, 29 Jul 2026 18:03:44 -0400 Subject: [PATCH 1/2] Create confirmed test user with fake activity points --- .../project_enter_create_test_users.py | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 osf/management/commands/project_enter_create_test_users.py diff --git a/osf/management/commands/project_enter_create_test_users.py b/osf/management/commands/project_enter_create_test_users.py new file mode 100644 index 00000000000..d1f45e3c86e --- /dev/null +++ b/osf/management/commands/project_enter_create_test_users.py @@ -0,0 +1,144 @@ +import logging +import random + +from django.core.management import CommandError +from django.core.management.base import BaseCommand +from django.db import transaction +from django.utils import timezone + +from osf.models import OSFUser, UserActivityCounter +from website.app import setup_django +from website.security import random_string + +setup_django() + + +logger = logging.getLogger(__name__) + + +def generate_users(prefix, suffix, domain, total): + """Generate usernames paired with full names. + """ + return { + f'{prefix}+{str(i).zfill(4)}+{suffix}@{domain}': f'{prefix}{str(i).zfill(4)} {suffix}{str(i).zfill(4)}' + for i in range(1, total + 1) + } + + +def create_confirmed_test_users( + prefix, + suffix='enter', + domain='cos.io', + total=100, + password=None, + set_activity=True, + dry_run=False +): + """Create a given number of confirmed users, with generated usernames and full names. For each created user, + optionally creates a matching UserActivityCounter entry with a random total between 1 and 100. + """ + created_users = [] + username_to_fullname = generate_users(prefix, suffix, domain, total) + + for raw_username, fullname in username_to_fullname.items(): + username = raw_username.lower().strip() + user_password = password or random_string(16) + if dry_run: + logger.info(f'Dry run: would create confirmed user "{username}" ({fullname}).') + continue + try: + user = OSFUser.create_confirmed( + username=username, + password=user_password, + fullname=fullname, + ) + except Exception as e: + logger.error(f'Failed to create confirmed user "{username}" ({fullname}): error={e}.') + continue + user.accepted_terms_of_service = timezone.now() + user.save() + if set_activity: + UserActivityCounter.objects.create( + _id=user._id, + action={}, + date={}, + total=random.randint(1, 100), + ) + created_users.append(user) + logger.info(f'Created confirmed user "{username}" ({fullname})') + + logger.info(f'Done. Created {len(created_users)} user(s); skipped {len(username_to_fullname) - len(created_users)}.') + return created_users + + +class Command(BaseCommand): + help = '''Create a given number of confirmed users, with generated usernames and full names. + + python3 manage.py project_enter_create_test_users longze --suffix enter --domain cos.io --total 100 + ''' + + def add_arguments(self, parser): + super().add_arguments(parser) + parser.add_argument( + 'prefix', + type=str, + help='Prefix for generated usernames and full names', + ) + parser.add_argument( + '--suffix', + type=str, + help='Suffix for generated usernames and full names', + ) + parser.add_argument( + '--domain', + type=str, + help='Domain for generated usernames', + ) + parser.add_argument( + '--total', + type=int, + help='Total number of users to create', + ) + parser.add_argument( + '--password', + type=str, + dest='password', + help='Password to set for every created user.', + ) + parser.add_argument( + '--no-activity', + action='store_true', + dest='no_activity', + help='Skip setting a random activity total for the created users', + ) + parser.add_argument( + '--dry', + action='store_true', + dest='dry_run', + help='Dry run; log what would happen without creating any users', + ) + + def handle(self, *args, **options): + prefix = options.get('prefix') + if not prefix: + raise CommandError('--prefix required') + suffix = options.get('suffix') + domain = options.get('domain') + total = options.get('total') + password = options.get('password') + set_activity = not options.get('no_activity', False) + dry_run = options.get('dry_run', False) + + if dry_run: + logger.info('This is a dry run; no users will be created.') + + with transaction.atomic(): + create_confirmed_test_users( + prefix, + suffix=suffix, + domain=domain, + total=total, + password=password, + set_activity=set_activity, + dry_run=dry_run, + ) From 6a9a2344c8295d02ad63f414f83562cb41284adc Mon Sep 17 00:00:00 2001 From: Longze Chen Date: Thu, 30 Jul 2026 11:23:15 -0400 Subject: [PATCH 2/2] Respond to CR with improvements --- ...sers.py => create_confirmed_test_users.py} | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) rename osf/management/commands/{project_enter_create_test_users.py => create_confirmed_test_users.py} (82%) diff --git a/osf/management/commands/project_enter_create_test_users.py b/osf/management/commands/create_confirmed_test_users.py similarity index 82% rename from osf/management/commands/project_enter_create_test_users.py rename to osf/management/commands/create_confirmed_test_users.py index d1f45e3c86e..436d8697af3 100644 --- a/osf/management/commands/project_enter_create_test_users.py +++ b/osf/management/commands/create_confirmed_test_users.py @@ -1,7 +1,6 @@ import logging import random -from django.core.management import CommandError from django.core.management.base import BaseCommand from django.db import transaction from django.utils import timezone @@ -37,7 +36,7 @@ def create_confirmed_test_users( """Create a given number of confirmed users, with generated usernames and full names. For each created user, optionally creates a matching UserActivityCounter entry with a random total between 1 and 100. """ - created_users = [] + created_user_ids = [] username_to_fullname = generate_users(prefix, suffix, domain, total) for raw_username, fullname in username_to_fullname.items(): @@ -57,46 +56,56 @@ def create_confirmed_test_users( continue user.accepted_terms_of_service = timezone.now() user.save() - if set_activity: - UserActivityCounter.objects.create( - _id=user._id, - action={}, - date={}, - total=random.randint(1, 100), - ) - created_users.append(user) logger.info(f'Created confirmed user "{username}" ({fullname})') + created_user_ids.append(user._id) + + if set_activity: + UserActivityCounter.objects.bulk_create( + [ + UserActivityCounter( + _id=_id, + action={}, + date={}, + total=random.randint(1, 100) + ) + for _id in created_user_ids + ], + ignore_conflicts=True, + ) - logger.info(f'Done. Created {len(created_users)} user(s); skipped {len(username_to_fullname) - len(created_users)}.') - return created_users + logger.info(f'Done. Created {len(created_user_ids)} user(s); skipped {len(username_to_fullname) - len(created_user_ids)}.') class Command(BaseCommand): help = '''Create a given number of confirmed users, with generated usernames and full names. - python3 manage.py project_enter_create_test_users longze --suffix enter --domain cos.io --total 100 + python3 manage.py create_confirmed_test_users --prefix longze --suffix enter --domain cos.io --total 100 ''' def add_arguments(self, parser): super().add_arguments(parser) parser.add_argument( - 'prefix', + '--prefix', type=str, + required=True, help='Prefix for generated usernames and full names', ) parser.add_argument( '--suffix', type=str, + default='enter', help='Suffix for generated usernames and full names', ) parser.add_argument( '--domain', type=str, + default='cos.io', help='Domain for generated usernames', ) parser.add_argument( '--total', type=int, + default=100, help='Total number of users to create', ) parser.add_argument( @@ -120,8 +129,6 @@ def add_arguments(self, parser): def handle(self, *args, **options): prefix = options.get('prefix') - if not prefix: - raise CommandError('--prefix required') suffix = options.get('suffix') domain = options.get('domain') total = options.get('total')