Repository: carpentries/amy

Issue:
Update `fake_database` tool
@gvwilson needs fake data by the end of this week - see [discussion on amy-dev](http://lists.software-carpentry.org/pipermail/amy-dev/2017-January/000216.html).

We have `fake_database` command line tool to generate fake data. Unfortunately, it's out of date and it doesn't generate fake training requests, workshop requests etc.
Update `fake_database` tool
@gvwilson needs fake data by the end of this week - see [discussion on amy-dev](http://lists.software-carpentry.org/pipermail/amy-dev/2017-January/000216.html).

We have `fake_database` command line tool to generate fake data. Unfortunately, it's out of date and it doesn't generate fake training requests, workshop requests etc.


Discussion:

Resolving patch:
diff --git a/workshops/management/commands/fake_database.py b/workshops/management/commands/fake_database.py
--- a/workshops/management/commands/fake_database.py
+++ b/workshops/management/commands/fake_database.py
@@ -1,10 +1,18 @@
-from datetime import timedelta, datetime
 import itertools
-import random
+from datetime import timedelta
+from random import (
+    random,
+    choice,
+    uniform,
+    sample as random_sample,
+    randint,
+)
 
+from django.contrib.auth.models import Group
 from django.core.management.base import BaseCommand
 from django_countries import countries as Countries
 from faker import Faker
+from faker.providers import BaseProvider
 
 from workshops.models import (
     Airport,
@@ -18,39 +26,103 @@
     Organization,
     Event,
     Task,
+    TrainingRequirement, 
+    TrainingProgress, 
+    TrainingRequest,
+    ProfileUpdateRequest, 
+    KnowledgeDomain, 
+    Membership, 
+    EventRequest,
+    AcademicLevel, 
+    ComputingExperienceLevel, 
+    DataAnalysisLevel, 
+    EventSubmission,
+    DCSelfOrganizedEventRequest, 
+    DCWorkshopDomain, 
+    DCWorkshopTopic,
+    Language,
+    InvoiceRequest,
 )
+from workshops.util import create_username
+
+
+def randbool(chances_of_true):
+    return random() < chances_of_true
+
+
+def sample(population, k=None):
+    """Behaves like random.sample, but if k is omitted, it default to 
+    randint(1, len(population)), so that a non-empty sample is returned."""
+
+    population = list(population)
+
+    if k is None:
+        k = randint(1, len(population))
+
+    return random_sample(population, k)
+
+
+class UniqueUrlProvider(BaseProvider):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.faker = Faker()
+        self._generated_urls = set()
+
+    def unique_url(self):
+        while True:
+            url = self.faker.url()
+            if url not in self._generated_urls:
+                break
+
+        self._generated_urls.add(url)
+        return url
 
 
 class Command(BaseCommand):
     help = 'Add fake data to the database.'
 
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.faker = Faker()
+        self.faker.add_provider(UniqueUrlProvider)
+
     def add_arguments(self, parser):
         parser.add_argument(
             '--seed', action='store', default=None,
             help='Provide an initial seed for randomization mechanism.',
         )
 
-    def fake_airports(self, faker, count=5):
+    def fake_airports(self):
         """Add some airports."""
         # we're not doing anything here, since:
         # 1. data migrations add some airports already
         # 2. we'll have more airports as fixtures as of #626
         pass
 
-    def fake_roles(self, faker):
-        """Provide fixed roles (before they end up in fixtures, see #626)."""
+    def fake_roles(self):
+        self.stdout.write('Generating fake roles...')
         roles = [
-            ('organizer', 'Workshop organizer'),
-            ('learner', 'Learner'),
-            ('host', 'Workshop host'),
-            ('instructor', 'Instructor'),
             ('helper', 'Helper'),
+            ('instructor', 'Instructor'),
+            ('host', 'Workshop host'),
+            ('learner', 'Learner'),
+            ('organizer', 'Workshop organizer'),
+            ('contributor', 'Contributed to lesson materials')
         ]
+        """Provide fixed roles (before they end up in fixtures, see #626)."""
         for name, verbose_name in roles:
             Role.objects.create(name=name, verbose_name=verbose_name)
 
-    def fake_tags(self, faker):
+    def fake_groups(self):
+        # Two groups are already in the migrations: Administrator
+        # and Steering Committee
+        self.stdout.write('Generating fake groups...')
+        Group.objects.create(name='invoicing')
+        Group.objects.create(name='trainers')
+
+    def fake_tags(self):
         """Provide fixed tags (before they end up in fixtures, see #626)."""
+        self.stdout.write('Generating fake tags...')
         tags = [
             ('SWC', 'Software Carpentry Workshop'),
             ('DC', 'Data Carpentry Workshop'),
@@ -61,10 +133,11 @@ def fake_tags(self, faker):
         for tag, details in tags:
             Tag.objects.create(name=tag, details=details)
 
-    def fake_badges(self, faker):
+    def fake_badges(self):
         """Provide fixed badges (before they end up in fixtures, see #626)."""
         # 4 badges are already in the migrations: swc-instructor,
         # dc-instructor, maintainer, and trainer
+        self.stdout.write('Generating fake badges...')
         badges = [
             ('creator', 'Creator',
              'Creating learning materials and other content'),
@@ -75,229 +148,528 @@ def fake_badges(self, faker):
         for name, title, criteria in badges:
             Badge.objects.create(name=name, title=title, criteria=criteria)
 
-    def fake_instructors(self, faker, count=5, add_badge=True,
-                         add_qualifications=True):
-        """Add a few people with random instructor badge, random airport, and
-        random qualification."""
-        airports = list(Airport.objects.all())
-        badges = list(Badge.objects.instructor_badges())
-        lessons = list(Lesson.objects.all())
-        for i in range(count):
-            user_name = faker.user_name()
-            emails = [faker.email(), faker.safe_email(), faker.free_email(),
-                      faker.company_email()]
-            gender = random.choice(Person.GENDER_CHOICES)[0]
-            if gender == 'F':
-                name = faker.first_name_female()
-                last_name = faker.last_name_female()
-            elif gender == 'M':
-                name = faker.first_name_male()
-                last_name = faker.last_name_male()
-            else:
-                name, last_name = faker.first_name(), faker.last_name()
-
-            person = Person.objects.create(
-                personal=name,
-                family=last_name,
-                email=random.choice(emails),
-                gender=gender,
-                may_contact=random.choice([True, False]),
-                airport=random.choice(airports),
-                github=user_name,
-                twitter=user_name,
-                url=faker.url(),
-                username=user_name,
-            )
-
-            if add_badge:
-                Award.objects.create(
-                    person=person, badge=random.choice(badges),
-                    awarded=faker.date_time_this_year(before_now=True,
-                                                      after_now=False).date(),
+    def fake_instructors(self, count=30):
+        self.stdout.write('Generating {} fake instructors...'.format(count))
+        for _ in range(count):
+            self.fake_person(is_instructor=True)
+
+    def fake_trainers(self, count=10):
+        self.stdout.write('Generating {} fake trainers...'.format(count))
+        for _ in range(count):
+            self.fake_person(is_instructor=True, is_trainer=True)
+
+    def fake_admins(self, count=10):
+        self.stdout.write('Generating {} fake admins...'.format(count))
+        for _ in range(count):
+            person = self.fake_person(is_instructor=randbool(0.5))
+            person.groups.add(choice(Group.objects.all()))
+            person.is_active = True
+            person.set_password(person.username)
+            person.save()
+
+    def fake_trainees(self, count=30):
+        self.stdout.write('Generating {} fake trainees '
+                          '(and their training progresses '
+                          'as well as training requests)...'.format(count))
+        for _ in range(count):
+            p = self.fake_person(is_instructor=randbool(0.1))
+            training = choice(Event.objects.ttt())
+            Task.objects.create(person=p, event=training,
+                                role=Role.objects.get(name='learner'))
+
+            self.fake_training_progresses(p, training)
+            if randbool(0.8):
+                self.fake_training_request(p)
+
+    def fake_training_progresses(self, p, training):
+        trainers = Person.objects.filter(award__badge__name='trainer')
+        for r in TrainingRequirement.objects.all():
+            if randbool(0.4):
+                if 'Homework' in r.name and randbool(0.5):
+                    state = 'n'
+                else:
+                    state = 'p' if randbool(0.95) else 'f'
+
+                evaluated_by = None if state == 'n' else choice(trainers)
+                event = training if r.name == 'Training' else None
+                url = self.faker.url() if 'Homework' in r.name else None
+                TrainingProgress.objects.create(
+                    trainee=p,
+                    requirement=r,
+                    evaluated_by=evaluated_by,
+                    state=state,
+                    discarded=randbool(0.05),
+                    event=event,
+                    url=url,
+                    notes='',
                 )
-            if add_qualifications:
-                for lesson in random.sample(lessons, 4):
-                    Qualification.objects.create(person=person, lesson=lesson)
-
-    def fake_noninstructors(self, faker, count=5):
-        """Add a few people who aren't instructors."""
-        return self.fake_instructors(
-            faker=faker, count=count, add_badge=False,
-            add_qualifications=False,
+
+    def fake_training_request(self, person_or_None):
+        if person_or_None is None:
+            state = 'p' if randbool(0.5) else 'd'
+            person = self.fake_person(is_instructor=False)
+        else:
+            state = 'a'
+            person = person_or_None
+
+        occupation = choice(ProfileUpdateRequest.OCCUPATION_CHOICES)[0]
+        req = TrainingRequest.objects.create(
+            state=state,
+            person=person_or_None,
+            group_name=self.faker.city() if randbool(0.1) else '',
+            personal=person.personal,
+            middle='',
+            family=person.family,
+            email=person.email,
+            github=person.github,
+            occupation=occupation,
+            occupation_other=self.faker.job() if occupation == '' else '',
+            affiliation=person.affiliation,
+            location=self.faker.city(),
+            country=choice(Countries)[0],
+            domains_other='',
+            gender=person.gender,
+            gender_other='',
+            previous_training=choice(
+                TrainingRequest.PREVIOUS_TRAINING_CHOICES)[0],
+            previous_training_other='',
+            previous_training_explanation=self.faker.text(),
+            previous_experience=choice(
+                TrainingRequest.PREVIOUS_EXPERIENCE_CHOICES)[0],
+            previous_experience_other='',
+            programming_language_usage_frequency=choice(
+                TrainingRequest.PROGRAMMING_LANGUAGE_USAGE_FREQUENCY_CHOICES)[0],
+            reason=self.faker.text(),
+            teaching_frequency_expectation=choice(
+                TrainingRequest.TEACHING_FREQUENCY_EXPECTATION_CHOICES)[0],
+            teaching_frequency_expectation_other='',
+            max_travelling_frequency=choice(
+                TrainingRequest.MAX_TRAVELLING_FREQUENCY_CHOICES)[0],
+            max_travelling_frequency_other='',
+            additional_skills=self.faker.job() if randbool(0.2) else '',
+            comment=self.faker.text() if randbool(0.3) else '',
+        )
+        req.domains = sample(KnowledgeDomain.objects.all())
+        req.previous_involvement = sample(Role.objects.all())
+
+        if person_or_None is None:
+            person.delete()
+
+    def fake_person(self, *, is_instructor, is_trainer=False):
+        airport = choice(Airport.objects.all())
+
+        email = choice([self.faker.email(),
+                        self.faker.safe_email(),
+                        self.faker.free_email(),
+                        self.faker.company_email()])
+
+        gender = choice(Person.GENDER_CHOICES)[0]
+
+        if gender == 'F':
+            personal_name = self.faker.first_name_female()
+            family_name = self.faker.last_name_female()
+        elif gender == 'M':
+            personal_name = self.faker.first_name_male()
+            family_name = self.faker.last_name_male()
+        else:
+            personal_name = self.faker.first_name()
+            family_name = self.faker.last_name()
+
+        social_username = self.faker.user_name()
+
+        if randbool(0.6):
+            # automatically generate username
+            username = create_username(personal_name, family_name)
+        else:
+            # assume that the username is provided by the person
+            username = social_username
+
+        github = social_username if randbool(0.5) else None
+        twitter = social_username if randbool(0.5) else None
+        url = self.faker.url() if randbool(0.5) else ''
+
+        person = Person.objects.create(
+            personal=personal_name,
+            family=family_name,
+            email=email,
+            gender=gender,
+            may_contact=randbool(0.5),
+            airport=airport,
+            twitter=twitter,
+            github=github,
+            url=url,
+            username=username,
         )
 
-    def fake_organizations(self, faker, count=5):
+        if is_instructor:
+            # Add one or more instructor badges
+            awards = []
+            badges = sample(Badge.objects.instructor_badges())
+            for badge in badges:
+                date = self.faker.date_time_between(start_date='-5y').date()
+                awards.append(Award(person=person, badge=badge, awarded=date))
+            Award.objects.bulk_create(awards)
+
+            if randbool(0.75):
+                # Add one or more qualifications
+                Qualification.objects.bulk_create(
+                    Qualification(person=person, lesson=lesson)
+                    for lesson in sample(Lesson.objects.all())
+                )
+
+        if is_trainer:
+            date = self.faker.date_time_between(start_date='-5y').date()
+            trainer = Badge.objects.get(name='trainer')
+            Award.objects.create(person=person, badge=trainer, awarded=date)
+
+        return person
+
+    def fake_organizations(self, count=10):
         """Add some organizations that host events."""
-        countries = list(Countries)
-        for i in range(count):
+        self.stdout.write('Generating {} fake organizations...'.format(count))
+
+        for _ in range(count):
             Organization.objects.create(
-                domain=faker.domain_name(),
-                fullname=faker.company(),
-                country=random.choice(countries)[0],
+                domain=self.faker.domain_name(),
+                fullname=self.faker.company(),
+                country=choice(Countries)[0],
             )
 
-    def fake_current_events(self, faker, count=5):
-        """Ongoing and upcoming events."""
-        twodays = timedelta(days=2)
-        organizations = list(Organization.objects.exclude(domain='self-organized'))
-        countries = list(Countries)
-
-        for i in range(count):
-            start = faker.date_time_this_year(before_now=False,
-                                              after_now=True).date()
-            Event.objects.create(
-                slug='{:%Y-%m-%d}-{}'.format(start, faker.slug()),
-                start=start,
-                end=start + twodays,
-                url=faker.url(),
-                host=random.choice(organizations),
-                # needed in order for event to be published
-                country=random.choice(countries)[0],
-                venue=faker.word().title(),
-                address=faker.sentence(nb_words=4, variable_nb_words=True),
-                latitude=random.uniform(-90, 90),
-                longitude=random.uniform(0, 180),
+    def fake_memberships(self, count=10):
+        self.stdout.write('Generating {} fake memberships...'.format(count))
+
+        for _ in range(count):
+            start = self.faker.date_time_between(start_date='-5y').date()
+            Membership.objects.create(
+                variant=choice(Membership.MEMBERSHIP_CHOICES),
+                agreement_start=start,
+                agreement_end=start + timedelta(days=365),
+                contribution_type=choice(Membership.CONTRIBUTION_CHOICES),
+                workshops_without_admin_fee_per_year=randint(5, 15),
+                self_organized_workshops_per_year=randint(5, 15),
+                notes='',
+                organization=choice(Organization.objects.all()),
             )
 
-    def fake_uninvoiced_events(self, faker, count=5):
+    def fake_current_events(self, count=5, **kwargs):
+        """Ongoing and upcoming events."""
+        self.stdout.write('Generating {} fake current events...'.format(count))
+
+        for _ in range(count):
+            self.fake_event(**kwargs)
+
+    def fake_uninvoiced_events(self, count=5):
         """Preferably in the past, and with 'uninvoiced' status."""
-        twodays = timedelta(days=2)
-        countries = list(Countries)
-        organizations = list(Organization.objects.exclude(domain='self-organized'))
-
-        for i in range(count):
-            start = faker.date_time_this_year(before_now=True,
-                                              after_now=False).date()
-            Event.objects.create(
-                slug='{:%Y-%m-%d}-{}'.format(start, faker.slug()),
-                start=start,
-                end=start + twodays,
-                url=faker.url(),
-                host=random.choice(organizations),
-                # needed in order for event to be published
-                country=random.choice(countries)[0],
-                venue=faker.word().title(),
-                address=faker.sentence(nb_words=4, variable_nb_words=True),
-                latitude=random.uniform(-90, 90),
-                longitude=random.uniform(0, 180),
-                # needed in order for event to be uninvoiced
-                invoice_status='not-invoiced',
-            )
+        self.stdout.write(
+            'Generating {} fake uninvoiced events...'.format(count))
+
+        for _ in range(count):
+            e = self.fake_event()
+            e.invoice_status = 'not-invoiced'
+            e.save()
 
-    def fake_unpublished_events(self, faker, count=5):
+    def fake_unpublished_events(self, count=5):
         """Events with missing location data (which is required for publishing
         them)."""
-        twodays = timedelta(days=2)
-        organizations = list(Organization.objects.exclude(domain='self-organized'))
-
-        for i in range(count):
-            start = faker.date_time_this_year(before_now=True,
-                                              after_now=True).date()
-            Event.objects.create(
-                slug='{:%Y-%m-%d}-{}'.format(start, faker.slug()),
-                start=start,
-                end=start + twodays,
-                url=faker.url(),
-                host=random.choice(organizations),
-            )
+        self.stdout.write(
+            'Generating {} fake unpublished events...'.format(count))
+
+        for _ in range(count):
+            self.fake_event(location_data=False)
 
-    def fake_self_organized_events(self, faker, count=5):
+    def fake_self_organized_events(self, count=5):
         """Full-blown events with 'self-organized' host."""
-        twodays = timedelta(days=2)
-        self_organized = Organization.objects.get(domain='self-organized')
-        countries = list(Countries)
-        invoice_statuses = Event.INVOICED_CHOICES
-
-        for i in range(count):
-            start = faker.date_time_this_year(before_now=True,
-                                              after_now=True).date()
-            Event.objects.create(
-                slug='{:%Y-%m-%d}-{}'.format(start, faker.slug()),
-                start=start,
-                end=start + twodays,
-                url=faker.url(),
-                host=self_organized,
-                # needed in order for event to be published
-                country=random.choice(countries)[0],
-                venue=faker.word().title(),
-                address=faker.sentence(nb_words=4, variable_nb_words=True),
-                latitude=random.uniform(-90, 90),
-                longitude=random.uniform(0, 180),
-                # needed in order for event to be uninvoiced
-                invoice_status=random.choice(invoice_statuses)[0],
-            )
+        self.stdout.write(
+            'Generating {} fake self organized events...'.format(count))
+
+        for _ in range(count):
+            e = self.fake_event(self_organized=True)
+            e.invoice_status = choice(Event.INVOICED_CHOICES)[0]
+            e.save()
+
+    def fake_ttt_events(self, count=10):
+        self.stdout.write(
+            'Generating {} fake train-the-trainer events...'.format(count))
+
+        for _ in range(count):
+            e = self.fake_event()
+            e.slug += '-ttt'
+            e.tags.set([Tag.objects.get(name='TTT')])
+            e.save()
+
+    def fake_event(self, *, location_data=True, self_organized=False,
+                   add_tags=True):
+        start = self.faker.date_time_between(start_date='-5y').date()
+        city = self.faker.city().replace(' ', '-').lower()
+        if self_organized:
+            org = Organization.objects.get(domain='self-organized')
+        else:
+            org = choice(Organization.objects.exclude(domain='self-organized'))
+
+        # The following line may result in IntegrityError from time to time,
+        # because we don't guarantee that the url is unique. In that case,
+        # simply create new database (rm db.sqlite3 && python manage.py migrate)
+        # and rerun fake_database command (python manage.py fake_database). Be
+        # aware that creating a database deletes all data in the existing
+        # database!
+        e = Event.objects.create(
+            slug='{:%Y-%m-%d}-{}'.format(start, city),
+            start=start,
+            end=start + timedelta(days=2),
+            url=self.faker.unique_url(),
+            host=org,
+            # needed in order for event to be published
+            country=choice(Countries)[0] if location_data else None,
+            venue=self.faker.word().title() if location_data else '',
+            address=self.faker.street_address() if location_data else '',
+            latitude=uniform(-90, 90) if location_data else None,
+            longitude=uniform(0, 180) if location_data else None,
+            metadata_changed=randbool(0.1),
+        )
+        if add_tags:
+            e.tags = sample(Tag.objects.exclude(name='TTT'), 2)
+        return e
+
+    def fake_tasks(self, count=120):
+        self.stdout.write('Generating {} fake tasks...'.format(count))
 
-    def fake_tasks(self, faker, count=50):
         events = Event.objects.all()
         persons = Person.objects.all()
         roles = Role.objects.all()
         all_possible = itertools.product(events, persons, roles)
 
-        for event, person, role in random.sample(list(all_possible), count):
+        for event, person, role in sample(all_possible, count):
             Task.objects.create(
                 event=event,
                 person=person,
                 role=role,
-                title=faker.sentence(nb_words=4, variable_nb_words=True),
-                url=faker.url(),
+                title=(self.faker.sentence(nb_words=4, variable_nb_words=True)
+                       if randbool(0.2) else ''),
+                url=self.faker.url() if randbool(0.2) else '',
             )
 
-    def fake_trainees(self, faker):
-        host = Organization.objects.all()[0]
-        tag = Tag.objects.get(name='TTT')
-        learner = Role.objects.get(name='learner')
-        swc_instructor = Badge.objects.get(name='swc-instructor')
-        dc_instructor = Badge.objects.get(name='dc-instructor')
-
-        # events
-        first_event = Event.objects.create(host=host, slug='first-ttt-event')
-        first_event.tags.add(tag)
-        first_event.save()
-
-        second_event = Event.objects.create(host=host, slug='second-ttt-event')
-        second_event.tags.add(tag)
-        second_event.save()
-
-        third_event = Event.objects.create(host=host, slug='third-ttt-event')
-        third_event.tags.add(tag)
-        third_event.save()
-
-        # persons
-        bob = Person.objects.create(username='bob-trainee',
-                                    personal='Bob', family='Smith')
-        alice = Person.objects.create(username='alice-trainee',
-                                      personal='Alice', family='Smith')
-        john = Person.objects.create(username='john-trainee',
-                                     personal='Alice', family='Smith')
-
-        # tasks
-        Task.objects.create(event=first_event, person=bob, role=learner)
-        Task.objects.create(event=first_event, person=alice, role=learner)
-        Task.objects.create(event=second_event, person=john, role=learner)
-
-        # awards
-        Award.objects.create(event=first_event, person=bob, badge=swc_instructor,
-                             awarded=datetime(2016, 4, 1))
-        Award.objects.create(event=third_event, person=bob, badge=dc_instructor,
-                             awarded=datetime(2016, 4, 1))
+    def fake_unmatched_training_requests(self, count=20):
+        self.stdout.write('Generating {} fake unmatched '
+                          'training requests...'.format(count))
 
-    def handle(self, *args, **options):
-        faker = Faker()
+        for _ in range(count):
+            self.fake_training_request(None)
+
+    def fake_duplicated_people(self, count=5):
+        self.stdout.write('Generating {} fake '
+                          'people duplications...'.format(count))
+
+        for _ in range(count):
+            p = Person.objects.order_by('?').first()
+            p.id = None
+            
+            # avoid integrity errors due to unique constraints
+            p.username = create_username(p.personal, p.family)
+            p.twitter = None 
+            p.github = None 
+            p.email = self.faker.email()
+
+            p.save()
 
+    def fake_workshop_requests(self, count=10):
+        self.stdout.write('Generating {} fake '
+                          'workshop requests...'.format(count))
+
+        for _ in range(count):
+            if randbool(0.5):
+                language = Language.objects.get(subtag='en')
+            else:
+                language = choice(Language.objects.all())
+
+            req = EventRequest.objects.create(
+                name=self.faker.name(),
+                email=self.faker.email(),
+                affiliation=self.faker.company(),
+                location=self.faker.city(),
+                country=choice(Countries)[0],
+                conference='',
+                preferred_date=str(self.faker.date_time_between(
+                    start_date='now', end_date='+1y').date()),
+                language=language,
+                workshop_type=choice(EventRequest.WORKSHOP_TYPE_CHOICES)[0],
+                approx_attendees=choice(
+                    EventRequest.ATTENDEES_NUMBER_CHOICES)[0],
+                attendee_domains_other='',
+                data_types=choice(EventRequest.DATA_TYPES_CHOICES)[0],
+                understand_admin_fee=True,
+                admin_fee_payment=choice(
+                    EventRequest.ADMIN_FEE_PAYMENT_CHOICES)[0],
+                fee_waiver_request=randbool(0.2),
+                cover_travel_accomodation=randbool(0.6),
+                travel_reimbursement=choice(
+                    EventRequest.TRAVEL_REIMBURSEMENT_CHOICES)[0],
+                travel_reimbursement_other='',
+                comment='',
+            )
+            req.attendee_domains = sample(KnowledgeDomain.objects.all())
+            req.attendee_academic_levels = sample(AcademicLevel.objects.all())
+            req.attendee_computing_levels = sample(
+                ComputingExperienceLevel.objects.all())
+            req.attendee_data_analysis_level = sample(
+                DataAnalysisLevel.objects.all())
+            req.save()
+
+    def fake_workshop_submissions(self, count=10):
+        self.stdout.write('Generating {} fake '
+                          'workshop submissions...'.format(count))
+
+        for _ in range(count):
+            EventSubmission.objects.create(
+                url=self.faker.url(),
+                contact_name=self.faker.name(),
+                contact_email=self.faker.email(),
+                self_organized=randbool(0.5),
+                notes='',
+            )
+
+    def fake_dc_selforganized_workshop_requests(self, count=5):
+        self.stdout.write('Generating {} fake dc self-organized '
+                          'workshops requests...'.format(count))
+
+        for _ in range(count):
+            date = self.faker.date_time_between(start_date='now',
+                                                end_date='+1y')
+
+            req = DCSelfOrganizedEventRequest.objects.create(
+                name=self.faker.name(),
+                email=self.faker.email(),
+                organization=self.faker.company(),
+                instructor_status=choice(
+                    DCSelfOrganizedEventRequest.INSTRUCTOR_CHOICES)[0],
+                is_partner=choice(
+                    DCSelfOrganizedEventRequest.PARTNER_CHOICES)[0],
+                is_partner_other='',
+                location=self.faker.city(),
+                country=choice(Countries)[0],
+                associated_conference='',
+                dates=str(date.date()),
+                domains_other='',
+                topics_other='',
+                payment=choice(DCSelfOrganizedEventRequest.PAYMENT_CHOICES)[0],
+                fee_waiver_reason='',
+                handle_registration=True,
+                distribute_surveys=True,
+                follow_code_of_conduct=True,
+            )
+            req.domains = sample(DCWorkshopDomain.objects.all())
+            req.topics = sample(DCWorkshopTopic.objects.all())
+            req.attendee_academic_levels = sample(AcademicLevel.objects.all())
+            req.attendee_data_analysis_level = sample(
+                DataAnalysisLevel.objects.all())
+
+    def fake_profile_update_requests(self, count=20):
+        self.stdout.write('Generating {} fake '
+                          'profile update requests...'.format(count))
+
+        for _ in range(count):
+            p = Person.objects.order_by('?').first()  # type: Person
+            req = ProfileUpdateRequest.objects.create(
+                active=randbool(0.5),
+                personal=(p.personal if randbool(0.9) 
+                          else self.faker.first_name()),
+                middle=p.middle,
+                family=p.family if randbool(0.9) else self.faker.last_name(),
+                email=p.email if randbool(0.8) else self.faker.email(),
+                affiliation=(p.affiliation if randbool(0.8) 
+                             else self.faker.company()),
+                airport_iata=(p.airport.iata 
+                              if randbool(0.9) and p.airport is not None
+                              else choice(Airport.objects.all()).iata),
+                occupation=choice(ProfileUpdateRequest.OCCUPATION_CHOICES)[0],
+                occupation_other='',
+                github=p.github or '',
+                twitter=p.twitter or '',
+                orcid=p.orcid or '',
+                website=p.url or '',
+                gender=choice(ProfileUpdateRequest.GENDER_CHOICES)[0],
+                gender_other='',
+                domains_other='',
+                lessons_other='',
+                notes='',
+            )
+            req.domains = (p.domains.all() if randbool(0.9) else 
+                           sample(KnowledgeDomain.objects.all()))
+            req.languages = (p.languages.all() if randbool(0.9) else 
+                             sample(Language.objects.all()))
+            req.lessons = (p.lessons.all() if randbool(0.9) else 
+                           sample(Lesson.objects.all()))
+
+    def fake_invoice_requests(self, count=10):
+        self.stdout.write('Generating {} fake '
+                          'invoice requests...'.format(count))
+
+        for _ in range(count):
+            status = choice(InvoiceRequest.STATUS_CHOICES)[0]
+            sent_date = (None if status == 'not-invoiced' else
+                         self.faker.date_time_between(start_date='-1y').date())
+            paid_date = (
+                self.faker.date_time_between(start_date=sent_date).date()
+                if status == 'paid' else None)
+            org = Organization.objects.order_by('?').first()
+            event = (Event.objects.order_by('?').first() 
+                     if randbool(0.8) else None)
+
+            req = InvoiceRequest.objects.create(
+                status=status,
+                sent_date=sent_date,
+                paid_date=paid_date,
+                organization=org,
+                reason=choice(InvoiceRequest.INVOICE_REASON)[0],
+                reason_other='',
+                date=(self.faker.date_time_between(start_date='-1y').date()
+                      if sent_date is None else sent_date),
+                event=event,
+                event_location='',
+                item_id='',
+                postal_number='',
+                contact_name=org.fullname,
+                contact_email=self.faker.email(),
+                contact_phone='',
+                full_address=self.faker.address(),
+                amount=choice([1000, 2000, 10000]),
+                currency=choice(InvoiceRequest.CURRENCY)[0],
+                currency_other='',
+                breakdown='',
+                vendor_form_required=
+                    choice(InvoiceRequest.VENDOR_FORM_CHOICES)[0],
+                vendor_form_link='',
+                form_W9=randbool(0.5),
+                receipts_sent=choice(InvoiceRequest.RECEIPTS_CHOICES)[0],
+                shared_receipts_link='',
+                notes='',
+            )
+
+    def handle(self, *args, **options):
         seed = options['seed']
         if seed is not None:
-            faker.seed(seed)
-
-        self.fake_airports(faker)
-        self.fake_roles(faker)
-        self.fake_tags(faker)
-        self.fake_badges(faker)
-        self.fake_instructors(faker)
-        self.fake_noninstructors(faker)
-        self.fake_organizations(faker)
-        self.fake_current_events(faker)
-        self.fake_uninvoiced_events(faker)
-        self.fake_unpublished_events(faker)
-        self.fake_self_organized_events(faker)
-        self.fake_tasks(faker)
-        self.fake_trainees(faker)
+            self.faker.seed(seed)
+
+        self.fake_airports()
+        self.fake_roles()
+        self.fake_groups()
+        self.fake_tags()
+        self.fake_badges()
+        self.fake_instructors()
+        self.fake_trainers()
+        self.fake_admins()
+        self.fake_organizations()
+        self.fake_memberships()
+        self.fake_current_events()
+        self.fake_uninvoiced_events()
+        self.fake_unpublished_events()
+        self.fake_self_organized_events()
+        self.fake_ttt_events()
+        self.fake_tasks()
+        self.fake_trainees()
+        self.fake_unmatched_training_requests()
+        self.fake_duplicated_people()
+        self.fake_workshop_requests()
+        self.fake_workshop_submissions()
+        self.fake_dc_selforganized_workshop_requests()
+        self.fake_profile_update_requests()
+        self.fake_invoice_requests()


Test patch:
diff --git a/workshops/test/test_commands.py b/workshops/test/test_commands.py
--- a/workshops/test/test_commands.py
+++ b/workshops/test/test_commands.py
@@ -3,6 +3,7 @@
 These commands are run via `./manage.py command`."""
 
 from datetime import date, datetime, time
+from io import StringIO
 import unittest
 from unittest.mock import MagicMock
 
@@ -34,80 +35,6 @@
 )
 
 
-class TestFakeDatabaseCommand(TestCase):
-    def setUp(self):
-        self.cmd = FakeDatabaseCommand()
-        self.seed = 12345
-        self.faker = Faker()
-        self.faker.seed(self.seed)
-
-    def test_no_airports_created(self):
-        """Make sure we don't create any airports.
-
-        We don't want to create them, because data migrations add some, and in
-        the future we want to add them via fixture (see #626)."""
-        airports_before = set(Airport.objects.all())
-        self.cmd.fake_airports(self.faker)
-        airports_after = set(Airport.objects.all())
-
-        self.assertEqual(airports_before, airports_after)
-
-    def test_new_roles_added(self):
-        """Make sure we add roles that are hard-coded. They'll end up in
-        fixtures in future (see #626)."""
-        roles = ['helper', 'instructor', 'host', 'learner', 'organizer',
-                 'contributor']
-        self.assertTrue(Role.objects.filter(name='contributor').exists())
-        self.assertFalse(Role.objects.filter(name__in=roles)
-                                     .exclude(name='contributor')
-                                     .exists())
-        self.cmd.fake_roles(self.faker)
-
-        self.assertEqual(set(roles),
-                         set(Role.objects.values_list('name', flat=True)))
-
-    def test_new_tags_added(self):
-        """Make sure we add tags that are hard-coded. They'll end up in
-        fixtures in future (see #626)."""
-        tags = ['SWC', 'DC', 'LC', 'WiSE', 'TTT', 'online', 'stalled',
-                'unresponsive', 'hackathon', 'cancelled']
-        self.assertNotEqual(set(tags),
-                            set(Tag.objects.values_list('name', flat=True)))
-
-        self.cmd.fake_tags(self.faker)
-        self.assertEqual(set(tags),
-                         set(Tag.objects.values_list('name', flat=True)))
-
-    def test_new_badges_added(self):
-        """Make sure we add badges that are hard-coded. They'll end up in
-        fixtures in future (see #626)."""
-        badges_pre = [
-            'swc-instructor', 'dc-instructor', 'maintainer', 'trainer',
-        ]
-        badges_post = ['creator', 'member', 'organizer']
-        self.assertEqual(set(badges_pre),
-                         set(Badge.objects.values_list('name', flat=True)))
-
-        self.cmd.fake_badges(self.faker)
-        self.assertEqual(set(badges_pre + badges_post),
-                         set(Badge.objects.values_list('name', flat=True)))
-
-    def test_database_populated(self):
-        """Make sure the database is getting populated."""
-        self.assertFalse(Person.objects.exists())
-        self.assertFalse(Organization.objects.exclude(domain='self-organized')
-                                     .exists())
-        self.assertFalse(Event.objects.exists())
-        self.assertFalse(Task.objects.exists())
-
-        call_command('fake_database', seed=self.seed)
-
-        self.assertTrue(Person.objects.exists())
-        self.assertTrue(Organization.objects.exclude(domain='self-organized').exists())
-        self.assertTrue(Event.objects.exists())
-        self.assertTrue(Task.objects.exists())
-
-
 class TestInstructorsActivityCommand(TestBase):
     def setUp(self):
         self.cmd = InstructorsActivityCommand()
@@ -222,10 +149,10 @@ def setUp(self):
         self.cmd = WebsiteUpdatesCommand()
         self.fake_cmd = FakeDatabaseCommand()
         self.seed = 12345
-        self.faker = Faker()
-        self.faker.seed(self.seed)
+        self.fake_cmd.faker.seed(self.seed)
+        self.fake_cmd.stdout = StringIO()
 
-        self.fake_cmd.fake_organizations(self.faker)
+        self.fake_cmd.fake_organizations()
 
         self.mocked_event_page = """
 <html><head>
@@ -286,7 +213,9 @@ def setUp(self):
 
     def test_getting_events(self):
         """Ensure only active events with URL are returned."""
-        self.fake_cmd.fake_current_events(self.faker, count=6)
+        self.fake_cmd.fake_current_events(count=6, add_tags=False)
+
+        Event.objects.all().update(start=date.today())
 
         # one active event with URL and one without
         e1, e2 = Event.objects.all()[0:2]