diff --git a/src/core/management/commands/create_conference.py b/src/core/management/commands/create_conference.py index 60ca31482bbd1df8b99e6c3246820f572d983512..e7dccc788fa2fc474d1a34bbbb53724ea23175f8 100644 --- a/src/core/management/commands/create_conference.py +++ b/src/core/management/commands/create_conference.py @@ -1,11 +1,16 @@ +from argparse import ArgumentTypeError, BooleanOptionalAction from datetime import datetime from zoneinfo import ZoneInfo +from django.contrib.auth.models import Group from django.core.management import call_command -from django.core.management.base import BaseCommand +from django.core.management.base import BaseCommand, CommandError +from django.utils.text import slugify -from core.models import Assembly, Conference, StaticPage, StaticPageNamespace +from core.models.assemblies import Assembly +from core.models.conference import Conference +from core.models.pages import StaticPage, StaticPageNamespace NS_INTRO = '_intro_' INTRO_PAGES = { @@ -72,7 +77,7 @@ INTRO_PAGES = { } -def seed_conference(conf: Conference, year: int = 0, force: bool = False): +def seed_conference(conf: Conference, year: int = 0): if Assembly.objects.filter(conference=conf).exists(): raise ValueError('Conference already has assemblies?!') @@ -328,27 +333,93 @@ def seed_conference(conf: Conference, year: int = 0, force: bool = False): ) +def _validate_date(s: str) -> datetime: + try: + return datetime.strptime(s, '%Y') # noqa: DTZ007 + except ValueError as exc: + raise ArgumentTypeError(f"Not a valid date: {s!r}. Use format 'YYYY'") from exc + + class Command(BaseCommand): def add_arguments(self, parser): - parser.add_argument('slug', type=str, nargs='?') - parser.add_argument('--name', type=str) - parser.add_argument('--year', type=int) - parser.add_argument('--bootstrap', action='store_true') + parser.add_argument( + 'name', + type=str, + nargs='?', + help='The name of the new conference', + ) + parser.add_argument( + '-s', + '--slug', + type=str, + help='The url slug of the new conference. If not given, it will be slugified from the name.', + default=None, + ) + parser.add_argument( + '-y', + '--year', + type=_validate_date, + help=( + 'The year of the CCC congress. If not given, it will use the current year.' + ' Setting this to 0 will skip any year-specific setup (e.g., nav entries).' + ), + default=None, + ) + parser.add_argument( + '--bootstrap', + action=BooleanOptionalAction, + help=("Initialize the conference permission groups (e.g. 'Assembly-Teams'). If not given, execution is determined by group presence in database."), + ) + parser.add_argument('-i', '--interactive', action='store_true', help='Ask for missing arguments', default=False) def handle(self, *args, **options): - if not (slug := options.get('slug')): - slug = input('SLUG of the new conference, e.g. "42c3": ') + interactive = options.get('interactive', False) + bootstrap = options.get('bootstrap') if not (name := options.get('name')): + if not interactive: + self.print_help('manage.py', 'create_conference') + raise CommandError('Missing required argument: name. Use --interactive to be asked for missing arguments.') name = input('NAME of the new conference (long title): ') - if not (year := options.get('year')): - year = input("YEAR of the CCC congress (leave empty if it's something else): ") - year = int(year or '0') - + if not (slug := options.get('slug')) and interactive: + slug = input('SLUG of the new conference, e.g. "42c3": ') + elif not (slug := options.get('slug')): + slug = slugify(name) + if not (year := options.get('year')) and interactive: + year_valid = False + while not year_valid: + year = input("YEAR of the CCC congress (leave empty if it's something else): ") + try: + year = datetime.strptime(year, '%Y') # noqa: DTZ007 + year_valid = True + except ValueError: + self.stderr.write('Invalid year format. Please use YYYY.') + elif not (year := options.get('year')): + year = datetime.now() # noqa: DTZ005 + year = year.year print(f'Creating conference {slug} ({name}) for year {year}') + # check if the conference already exists + if Conference.objects.filter(slug=slug).exists(): + raise CommandError(f'Conference with slug "{slug}" already exists.') + + if bootstrap and Group.objects.exists(): + answer = None + while answer not in ['y', 'n', '']: + answer = input('Groups are already present in the database. Do you still want to bootstrap? (y/[n]) ') + + bootstrap = answer.lower() == 'y' + if bootstrap: + self.stdout.write(self.style.WARNING('Groups will be loaded and may be duplicated!')) + else: + self.stdout.write(self.style.WARNING('Bootstrap will not be applied.')) + # load our bootstrap fixtures - if options.get('bootstrap'): + if bootstrap or (bootstrap is None and not Group.objects.exists()): + self.stdout.write('Loading bootstrap data...') call_command('loaddata', 'bootstrap_auth_groups.json') + self.stdout.write(self.style.SUCCESS('Bootstrap data loaded.')) + elif bootstrap is None and Group.objects.exists(): + self.stdout.write(self.style.SUCCESS('Groups are already present in the database.')) # create the new conference conf = Conference.objects.create(