From fef77e8a39e192db6d54d978e4c3908710d72e4c Mon Sep 17 00:00:00 2001 From: Jacob Coffee Date: Thu, 5 Feb 2026 14:42:03 -0600 Subject: [PATCH 1/3] source some test commands for local dev use --- .../management/commands/create_test_events.py | 282 ++++++++++++++++++ .../commands/create_test_sponsor_data.py | 230 ++++++++++++++ 2 files changed, 512 insertions(+) create mode 100644 events/management/commands/create_test_events.py create mode 100644 sponsors/management/commands/create_test_sponsor_data.py diff --git a/events/management/commands/create_test_events.py b/events/management/commands/create_test_events.py new file mode 100644 index 000000000..36fbad05c --- /dev/null +++ b/events/management/commands/create_test_events.py @@ -0,0 +1,282 @@ +import datetime +from django.conf import settings +from django.core.management.base import BaseCommand, CommandError +from django.utils import timezone +from events.models import Calendar, Event, EventCategory, EventLocation, OccurringRule, RecurringRule +from dateutil.rrule import WEEKLY, MONTHLY, YEARLY, DAILY + + +class Command(BaseCommand): + help = 'Creates test events for the events app (development only)' + + def add_arguments(self, parser): + parser.add_argument( + '--force', + action='store_true', + help='Force execution even in non-DEBUG mode (use with extreme caution)', + ) + + def handle(self, *args, **options): + # Production safety check + if not settings.DEBUG and not options['force']: + raise CommandError( + "This command cannot be run in production (DEBUG=False). " + "This command creates test data and should only be used in development environments." + ) + # Create main calendar + main_calendar, created = Calendar.objects.get_or_create( + slug='python-events', + defaults={ + 'name': 'Python Events', + 'description': 'Main Python community events calendar', + } + ) + self.stdout.write(f"Main calendar {'created' if created else 'already exists'}: {main_calendar}") + + # Create additional calendars + user_group_calendar, _ = Calendar.objects.get_or_create( + slug='user-group-events', + defaults={ + 'name': 'User Group Events', + 'description': 'Python user group meetups and events', + } + ) + + conference_calendar, _ = Calendar.objects.get_or_create( + slug='conferences', + defaults={ + 'name': 'Python Conferences', + 'description': 'Major Python conferences worldwide', + } + ) + + # Create categories + meetup_category, _ = EventCategory.objects.get_or_create( + slug='meetup', + calendar=main_calendar, + defaults={'name': 'Meetup'} + ) + + conference_category, _ = EventCategory.objects.get_or_create( + slug='conference', + calendar=main_calendar, + defaults={'name': 'Conference'} + ) + + workshop_category, _ = EventCategory.objects.get_or_create( + slug='workshop', + calendar=main_calendar, + defaults={'name': 'Workshop'} + ) + + sprint_category, _ = EventCategory.objects.get_or_create( + slug='sprint', + calendar=main_calendar, + defaults={'name': 'Sprint'} + ) + + # Create locations + location_sf, _ = EventLocation.objects.get_or_create( + name='San Francisco Python Meetup Space', + calendar=main_calendar, + defaults={ + 'address': '123 Market St, San Francisco, CA 94105', + 'url': 'https://example.com/sf-venue' + } + ) + + location_ny, _ = EventLocation.objects.get_or_create( + name='NYC Python Center', + calendar=main_calendar, + defaults={ + 'address': '456 Broadway, New York, NY 10013', + 'url': 'https://example.com/ny-venue' + } + ) + + location_london, _ = EventLocation.objects.get_or_create( + name='London Tech Hub', + calendar=main_calendar, + defaults={ + 'address': '789 Oxford Street, London, UK', + 'url': 'https://example.com/london-venue' + } + ) + + location_online, _ = EventLocation.objects.get_or_create( + name='Online', + calendar=main_calendar, + defaults={ + 'address': '', + 'url': 'https://zoom.us' + } + ) + + # Current time reference + now = timezone.now() + + # Create past, current, and future events + + # 1. Past conference (3 months ago) + past_event = Event.objects.create( + title='PyCon US 2024', + calendar=conference_calendar, + description='The largest annual gathering for the Python community.', + venue=location_sf, + featured=True + ) + past_event.categories.add(conference_category) + OccurringRule.objects.create( + event=past_event, + dt_start=now - timezone.timedelta(days=90), + dt_end=now - timezone.timedelta(days=87) + ) + + # 2. Ongoing workshop series (weekly) + workshop_event = Event.objects.create( + title='Python for Data Science Workshop', + calendar=main_calendar, + description='Weekly hands-on workshop covering pandas, numpy, and data visualization.', + venue=location_online + ) + workshop_event.categories.add(workshop_category) + RecurringRule.objects.create( + event=workshop_event, + begin=now - timezone.timedelta(days=30), + finish=now + timezone.timedelta(days=90), + duration='2 hours', + interval=1, + frequency=WEEKLY + ) + + # 3. Monthly user group meetup + meetup_event = Event.objects.create( + title='NYC Python Meetup', + calendar=user_group_calendar, + description='Monthly gathering of Python enthusiasts in New York City. Lightning talks and networking.', + venue=location_ny + ) + meetup_event.categories.add(meetup_category) + RecurringRule.objects.create( + event=meetup_event, + begin=now - timezone.timedelta(days=60), + finish=now + timezone.timedelta(days=365), + duration='3 hours', + interval=1, + frequency=MONTHLY + ) + + # 4. Upcoming sprint (next week) + sprint_event = Event.objects.create( + title='Django Sprint Weekend', + calendar=main_calendar, + description='Two-day sprint to contribute to Django core and ecosystem packages.', + venue=location_sf + ) + sprint_event.categories.add(sprint_category) + OccurringRule.objects.create( + event=sprint_event, + dt_start=now + timezone.timedelta(days=7), + dt_end=now + timezone.timedelta(days=9) + ) + + # 5. Major upcoming conference + pycon_europe = Event.objects.create( + title='EuroPython 2025', + calendar=conference_calendar, + description='The official European Python conference bringing together Python users from across Europe and beyond.', + venue=location_london, + featured=True + ) + pycon_europe.categories.add(conference_category) + OccurringRule.objects.create( + event=pycon_europe, + dt_start=now + timezone.timedelta(days=120), + dt_end=now + timezone.timedelta(days=125) + ) + + # 6. Daily coding challenge (for next 30 days) + daily_challenge = Event.objects.create( + title='Python Daily Coding Challenge', + calendar=main_calendar, + description='Solve a new Python coding challenge every day. Perfect for interview prep!', + venue=location_online + ) + daily_challenge.categories.add(workshop_category) + RecurringRule.objects.create( + event=daily_challenge, + begin=now, + finish=now + timezone.timedelta(days=30), + duration='1 hour', + interval=1, + frequency=DAILY + ) + + # 7. London Python meetup (monthly) + london_meetup = Event.objects.create( + title='London Python User Group', + calendar=user_group_calendar, + description='Monthly meetup for Pythonistas in London. Talks, tutorials, and pub discussions.', + venue=location_london + ) + london_meetup.categories.add(meetup_category) + RecurringRule.objects.create( + event=london_meetup, + begin=now - timezone.timedelta(days=45), + finish=now + timezone.timedelta(days=180), + duration='2 hours 30 min', + interval=1, + frequency=MONTHLY + ) + + # 8. Annual Python conference + pydata_global = Event.objects.create( + title='PyData Global Conference', + calendar=conference_calendar, + description='The global conference on data science, machine learning, and AI with Python.', + venue=location_online, + featured=True + ) + pydata_global.categories.add(conference_category) + OccurringRule.objects.create( + event=pydata_global, + dt_start=now + timezone.timedelta(days=60), + dt_end=now + timezone.timedelta(days=63), + all_day=True + ) + + # 9. Weekend workshop + ml_workshop = Event.objects.create( + title='Machine Learning with Python: From Zero to Hero', + calendar=main_calendar, + description='Intensive weekend workshop covering ML fundamentals with scikit-learn and TensorFlow.', + venue=location_sf + ) + ml_workshop.categories.add(workshop_category) + OccurringRule.objects.create( + event=ml_workshop, + dt_start=now + timezone.timedelta(days=14), + dt_end=now + timezone.timedelta(days=16) + ) + + # 10. Recurring online office hours + office_hours = Event.objects.create( + title='Python Core Dev Office Hours', + calendar=main_calendar, + description='Weekly office hours with Python core developers. Get your questions answered!', + venue=location_online + ) + RecurringRule.objects.create( + event=office_hours, + begin=now - timezone.timedelta(days=14), + finish=now + timezone.timedelta(days=90), + duration='1 hour', + interval=1, + frequency=WEEKLY + ) + + self.stdout.write(self.style.SUCCESS(f'Successfully created test events across {Calendar.objects.count()} calendars')) + self.stdout.write(f'Total events: {Event.objects.count()}') + self.stdout.write(f'Featured events: {Event.objects.filter(featured=True).count()}') + self.stdout.write(f'Events with recurring rules: {Event.objects.filter(recurring_rules__isnull=False).distinct().count()}') + self.stdout.write(f'Events with single occurrences: {Event.objects.filter(occurring_rule__isnull=False).count()}') \ No newline at end of file diff --git a/sponsors/management/commands/create_test_sponsor_data.py b/sponsors/management/commands/create_test_sponsor_data.py new file mode 100644 index 000000000..96bc08065 --- /dev/null +++ b/sponsors/management/commands/create_test_sponsor_data.py @@ -0,0 +1,230 @@ +""" +Management command to create test sponsor and contract data for testing (development only) +""" +from datetime import date, timedelta +import uuid +from django.conf import settings +from django.core.management.base import BaseCommand, CommandError +from django.contrib.auth import get_user_model + +User = get_user_model() +from sponsors.models import ( + Sponsor, Sponsorship, SponsorshipPackage, SponsorshipBenefit, + Contract, SponsorContact, SponsorBenefit +) + + +class Command(BaseCommand): + help = 'Create test sponsor and contract data for testing contract display (development only)' + + def add_arguments(self, parser): + parser.add_argument( + '--clean', + action='store_true', + help='Delete existing test data before creating new data', + ) + parser.add_argument( + '--force', + action='store_true', + help='Force execution even in non-DEBUG mode (use with extreme caution)', + ) + + def handle(self, *args, **options): + # Production safety check + if not settings.DEBUG and not options['force']: + raise CommandError( + "This command cannot be run in production (DEBUG=False). " + "This command creates test data and should only be used in development environments." + ) + if options['clean']: + self.stdout.write('Cleaning existing test data...') + # Clean up test data + Contract.objects.filter(sponsorship__sponsor__name__startswith='Test Sponsor').delete() + Sponsorship.objects.filter(sponsor__name__startswith='Test Sponsor').delete() + Sponsor.objects.filter(name__startswith='Test Sponsor').delete() + SponsorshipPackage.objects.filter(name__startswith='Test Package').delete() + + # Generate unique identifiers for this run + run_id = str(uuid.uuid4())[:8] + self.stdout.write(f'Creating test sponsor and contract data (Run ID: {run_id})...') + + # Create a test user for relationships + user, created = User.objects.get_or_create( + username='test_sponsor_user', + defaults={ + 'email': 'test@sponsor.com', + 'first_name': 'Test', + 'last_name': 'User' + } + ) + + # Create a test sponsorship package (reuse existing if available) + current_year = date.today().year - 5 + package, created = SponsorshipPackage.objects.get_or_create( + name='Test Package - Gold', + year=current_year, + defaults={ + 'sponsorship_amount': 10000, + 'advertise': True, + 'logo_dimension': 200, + 'slug': 'test-gold' + } + ) + + # Create a unique test sponsor for each run + sponsor_name = f'Test Sponsor Corp {run_id}' + sponsor = Sponsor.objects.create( + name=sponsor_name, + description=f'A test sponsor company for development and testing ({run_id})', + landing_page_url=f'https://test-sponsor-{run_id}.com', + twitter_handle=f'@testsponsor{run_id}', + primary_phone='+1-555-0123', + mailing_address_line_1=f'123 Test Street {run_id}', + city='Test City', + state='Test State', + postal_code='12345', + country='US' + ) + + # Create a sponsor contact + contact = SponsorContact.objects.create( + sponsor=sponsor, + name=f'John Test Contact {run_id}', + email=f'john@testsponsor{run_id}.com', + phone='+1-555-0123', + primary=True + ) + + # Create multiple sponsorships with different statuses for testing + start_date = date.today() + end_date = start_date + timedelta(days=365) + current_test_year = start_date.year + 1 + + sponsorships = [] + + # 1. Applied sponsorship (can be withdrawn) + sponsorship_applied = Sponsorship.objects.create( + sponsor=sponsor, + package=package, + status=Sponsorship.APPLIED, + applied_on=start_date, + for_modified_package=False, + year=current_test_year, + sponsorship_fee=package.sponsorship_amount + ) + sponsorships.append(('Applied', sponsorship_applied)) + + # 2. Approved sponsorship (can be cancelled) + sponsorship_approved = Sponsorship.objects.create( + sponsor=sponsor, + package=package, + status=Sponsorship.APPROVED, + start_date=start_date, + end_date=end_date, + applied_on=start_date - timedelta(days=5), + approved_on=start_date, + for_modified_package=False, + year=current_test_year, + sponsorship_fee=package.sponsorship_amount + ) + sponsorships.append(('Approved', sponsorship_approved)) + + # 3. Finalized sponsorship (complete workflow) + sponsorship_finalized = Sponsorship.objects.create( + sponsor=sponsor, + package=package, + status=Sponsorship.FINALIZED, + start_date=start_date, + end_date=end_date, + applied_on=start_date - timedelta(days=10), + approved_on=start_date - timedelta(days=5), + finalized_on=start_date, + for_modified_package=False, + year=current_test_year, + sponsorship_fee=package.sponsorship_amount + ) + sponsorships.append(('Finalized', sponsorship_finalized)) + + + # 5. Cancelled sponsorship (NEW STATUS) + # sponsorship_cancelled = Sponsorship.objects.create( + # sponsor=sponsor, + # package=package, + # status=Sponsorship.CANCELLED, + # start_date=start_date, + # end_date=end_date, + # applied_on=start_date - timedelta(days=20), + # approved_on=start_date - timedelta(days=15), + # cancelled_on=start_date - timedelta(days=5), + # for_modified_package=False, + # year=current_test_year, + # sponsorship_fee=package.sponsorship_amount, + # locked=True + # ) + # sponsorships.append(('Cancelled', sponsorship_cancelled)) + + # 6. Rejected sponsorship (for completeness) + sponsorship_rejected = Sponsorship.objects.create( + sponsor=sponsor, + package=package, + status=Sponsorship.REJECTED, + applied_on=start_date - timedelta(days=25), + rejected_on=start_date - timedelta(days=20), + for_modified_package=False, + year=current_test_year, + sponsorship_fee=package.sponsorship_amount, + locked=True + ) + sponsorships.append(('Rejected', sponsorship_rejected)) + + # Use the finalized sponsorship for contract creation + sponsorship = sponsorship_finalized + + # Create a contract for the sponsorship with a document + from django.core.files.base import ContentFile + + # Create a simple PDF-like content for testing + dummy_pdf_content = b"%PDF-1.4\n1 0 obj\n<<\n/Type /Catalog\n/Pages 2 0 R\n>>\nendobj\n" + + contract = Contract.objects.create( + sponsorship=sponsorship, + status=Contract.AWAITING_SIGNATURE, # Status that shows download links + revision=1, + sponsor_info=f'{sponsor_name}\n123 Test Street {run_id}\nTest City, Test State 12345\nUS', + sponsor_contact=f'John Test Contact {run_id}\njohn@testsponsor{run_id}.com\n+1-555-0123' + ) + + # Add a document to the contract + contract.document.save( + f'test_contract_{run_id}.pdf', + ContentFile(dummy_pdf_content), + save=True + ) + + self.stdout.write( + self.style.SUCCESS('Successfully created test data:') + ) + self.stdout.write(f'- Sponsor: {sponsor.name} (ID: {sponsor.id})') + self.stdout.write(f'- Package: {package.name}') + self.stdout.write(f'- Contract: {contract} (ID: {contract.id})') + + self.stdout.write('\n📋 Created Sponsorships with ALL status types:') + for status_name, sponsorship_obj in sponsorships: + self.stdout.write(f' • {status_name}: ID {sponsorship_obj.id} - {sponsorship_obj}') + + self.stdout.write('\n🧪 Testing URLs:') + self.stdout.write('Admin Sponsorships List:') + self.stdout.write(' http://localhost:8000/admin/sponsors/sponsorship/') + + self.stdout.write('\nTest NEW Status Transitions:') + applied_id = sponsorships[0][1].id + approved_id = sponsorships[1][1].id + self.stdout.write(f' • Cancel Approved: http://localhost:8000/admin/sponsors/sponsorship/{approved_id}/cancel') + + self.stdout.write('\nView Sponsorship Details:') + for status_name, sponsorship_obj in sponsorships: + self.stdout.write(f' • {status_name}: http://localhost:8000/admin/sponsors/sponsorship/{sponsorship_obj.id}/change/') + + self.stdout.write('\nContract Display:') + self.stdout.write(f' http://localhost:8000/admin/sponsors/contract/{contract.id}/preview') + self.stdout.write(f' http://localhost:8000/admin/sponsors/contract/{contract.id}/change/') \ No newline at end of file From 75ecb8aab13647da5ea40d20141aac9c32e1ad42 Mon Sep 17 00:00:00 2001 From: Jacob Coffee Date: Thu, 5 Feb 2026 14:44:39 -0600 Subject: [PATCH 2/3] dont include scripts though --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a9eca9d19..e088851ed 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ __pycache__ .DS_Store .envrc .state/ +.scripts/ \ No newline at end of file From 027afdb309b2b79e65d40d3f5b74171ea5202732 Mon Sep 17 00:00:00 2001 From: Jacob Coffee Date: Thu, 5 Feb 2026 14:44:59 -0600 Subject: [PATCH 3/3] no period --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e088851ed..0b42b8f1d 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,4 @@ __pycache__ .DS_Store .envrc .state/ -.scripts/ \ No newline at end of file +scripts/ \ No newline at end of file