forked from 47-studio-org/backend
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Generate seed data for dev/staging using factories (codebuddies#129)
* add unique constraint on resource, tag This constraint prevents us from tagging a resource with the same tag more than once when we're creating TaggedItems objects via factories. This is already applied as a soft-constraint at the manager level when creating via `resource.tags.add()`, but the factories bypass it. This applies it at the DB level * add a management command to generate fake data for dev * add tests * update docs * delete old json fixtures
- Loading branch information
Showing
16 changed files
with
233 additions
and
6,000 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class CoreConfig(AppConfig): | ||
name = 'core' |
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import pprint | ||
import random | ||
from argparse import ArgumentTypeError | ||
from functools import partial | ||
|
||
from django.core.management.base import BaseCommand | ||
from factory import PostGenerationMethodCall, create_batch | ||
|
||
from users.factories import UserFactory | ||
from users.models import User | ||
from resources.factories import ResourceFactory | ||
from resources.models import Resource | ||
from tagging.factories import CustomTagFactory, TaggedItemsFactory | ||
from tagging.models import CustomTag, TaggedItems | ||
|
||
|
||
class Command(BaseCommand): | ||
|
||
help = "Initialize the DB with some random fake data for testing and development" | ||
|
||
def add_arguments(self, parser): | ||
parser.add_argument( | ||
"--clear-db", | ||
action="store_true", | ||
dest="clear-db", | ||
help="Clear existing data from the DB before creating test data", | ||
) | ||
|
||
def check_min_int(min, value): | ||
ivalue = int(value) | ||
if ivalue < min: | ||
raise ArgumentTypeError(f"must be at least {min}") | ||
return ivalue | ||
|
||
parser.add_argument( | ||
'--num-users', | ||
type=partial(check_min_int, 1), | ||
required=False, | ||
default=10, | ||
dest="num-users", | ||
help="Number of `User` objects to create (default 10)" | ||
) | ||
parser.add_argument( | ||
'--num-tags', | ||
type=partial(check_min_int, 1), | ||
required=False, | ||
default=10, | ||
dest="num-tags", | ||
help="Number of `Tag` objects to create (default 10)" | ||
) | ||
parser.add_argument( | ||
'--num-resources', | ||
type=partial(check_min_int, 2), | ||
required=False, | ||
default=10, | ||
dest="num-resources", | ||
help="Number of `Resource` objects to create (default 10)" | ||
) | ||
|
||
def teardown(self): | ||
TaggedItems.objects.all().delete() | ||
CustomTag.objects.all().delete() | ||
Resource.objects.all().delete() | ||
User.objects.all().delete() | ||
|
||
def create_users(self, num): | ||
# Create random users. | ||
# All users have the password 'codebuddies'. | ||
return [ | ||
UserFactory( | ||
password=PostGenerationMethodCall('set_password', 'codebuddies') | ||
) for _ in range(0, num) | ||
] | ||
|
||
def create_tags(self, num): | ||
initial_tag_count = CustomTag.objects.all().count() | ||
tags = set() | ||
while CustomTag.objects.all().count() < (initial_tag_count + num): | ||
tags.add(CustomTagFactory()) | ||
return list(tags) | ||
|
||
def create_resources(self, num): | ||
return create_batch(ResourceFactory, num, user=random.choice(self.users)) | ||
|
||
def tag_resources(self): | ||
tagged_items = [] | ||
# Assign one resource zero tags, but | ||
# assign each other resource at least one tag | ||
for resource in self.resources[:-1]: | ||
for _ in range(0, random.randrange(1, 4)): | ||
tag = random.choice(self.tags) | ||
tagged_items.append( | ||
TaggedItemsFactory(content_object=resource, tag=tag), | ||
) | ||
return tagged_items | ||
|
||
def print_list(self, lst): | ||
pp = pprint.PrettyPrinter(indent=4) | ||
pp.pprint(lst) | ||
|
||
def print_summary(self, verbosity): | ||
self.stdout.write(f'Created {len(self.users)} Users.') | ||
if verbosity >= 2: | ||
self.print_list(self.users) | ||
self.stdout.write(f'Created {len(self.tags)} Tags.') | ||
if verbosity >= 2: | ||
self.print_list(self.tags) | ||
self.stdout.write(f'Created {len(self.resources)} Resources.') | ||
if verbosity >= 2: | ||
self.print_list(self.resources) | ||
self.stdout.write(f'Created {len(self.tagged_items)} TaggedItems.') | ||
if verbosity >= 2: | ||
self.print_list(self.tagged_items) | ||
|
||
def handle(self, *args, **kwargs): | ||
if kwargs['clear-db']: | ||
self.stdout.write('Clearing existing data..') | ||
self.teardown() | ||
|
||
self.stdout.write('Creating test data..') | ||
self.users = self.create_users(kwargs['num-users']) | ||
self.tags = self.create_tags(kwargs['num-tags']) | ||
self.resources = self.create_resources(kwargs['num-resources']) | ||
self.tagged_items = self.tag_resources() | ||
|
||
self.print_summary(kwargs['verbosity']) | ||
|
||
self.stdout.write('..done') |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
from io import StringIO | ||
|
||
from django.core.management import call_command | ||
from django.test import TestCase | ||
|
||
from core.management.commands.init_data import Command | ||
from users.models import User | ||
from resources.models import Resource | ||
from tagging.models import CustomTag, TaggedItems | ||
|
||
|
||
class InitDataTest(TestCase): | ||
|
||
def test_defaults(self): | ||
out = StringIO() | ||
call_command("init_data", stdout=out) | ||
log = out.getvalue() | ||
|
||
self.assertEqual(10, User.objects.all().count()) | ||
self.assertIn('Created 10 Users.', log) | ||
|
||
self.assertEqual(10, CustomTag.objects.all().count()) | ||
self.assertIn('Created 10 Tags.', log) | ||
|
||
self.assertEqual(10, Resource.objects.all().count()) | ||
self.assertIn('Created 10 Resources.', log) | ||
|
||
# The number of TaggedItems we will insert is non-deterministic, | ||
# but the lower bound is 9 | ||
self.assertGreaterEqual(TaggedItems.objects.all().count(), 9) | ||
|
||
def test_params(self): | ||
out = StringIO() | ||
call_command("init_data", stdout=out, **{ | ||
'num-users': 24, | ||
'num-tags': 26, | ||
'num-resources': 28, | ||
}) | ||
log = out.getvalue() | ||
|
||
self.assertEqual(24, User.objects.all().count()) | ||
self.assertIn('Created 24 Users.', log) | ||
|
||
self.assertEqual(26, CustomTag.objects.all().count()) | ||
self.assertIn('Created 26 Tags.', log) | ||
|
||
self.assertEqual(28, Resource.objects.all().count()) | ||
self.assertIn('Created 28 Resources.', log) | ||
|
||
self.assertGreaterEqual(TaggedItems.objects.all().count(), 27) | ||
|
||
def test_clear_db(self): | ||
out = StringIO() | ||
# set up some non-zero amount of data and assert it is there | ||
call_command("init_data", stdout=out) | ||
self.assertGreaterEqual(User.objects.all().count(), 1) | ||
self.assertGreaterEqual(CustomTag.objects.all().count(), 1) | ||
self.assertGreaterEqual(Resource.objects.all().count(), 1) | ||
self.assertGreaterEqual(TaggedItems.objects.all().count(), 1) | ||
|
||
cmd = Command() | ||
# clear it | ||
cmd.teardown() | ||
|
||
# check its all gone | ||
self.assertEqual(0, User.objects.all().count()) | ||
self.assertEqual(0, CustomTag.objects.all().count()) | ||
self.assertEqual(0, Resource.objects.all().count()) | ||
self.assertEqual(0, TaggedItems.objects.all().count()) |
Oops, something went wrong.