Refactor "fake" command.

This commit simplifies the "fake" code code and makes the data much more realistic by preventing activity intersection. It also uses atomic transactions to make fake data generation _much_ faster on slow computers (like my old laptop!).
This commit is contained in:
Christopher Charbonneau Wells 2017-11-11 13:01:40 -05:00
parent d2227e7747
commit 5dec231147
1 changed files with 123 additions and 92 deletions

View File

@ -5,6 +5,7 @@ from random import choice, randint, uniform
from datetime import timedelta from datetime import timedelta
from decimal import Decimal from decimal import Decimal
from django.db import transaction
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.utils import timezone from django.utils import timezone
@ -19,6 +20,10 @@ class Command(BaseCommand):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Command, self).__init__(*args, **kwargs) super(Command, self).__init__(*args, **kwargs)
self.faker = Factory.create() self.faker = Factory.create()
self.child = None
self.weight = None
self.time = None
self.time_now = timezone.localtime()
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument( parser.add_argument(
@ -39,132 +44,158 @@ class Command(BaseCommand):
children = int(kwargs['children']) or 1 children = int(kwargs['children']) or 1
days = int(kwargs['days']) or 31 days = int(kwargs['days']) or 31
# User first day of data that will created for birth date.
birth_date = (timezone.localtime() - timedelta(days=days)) birth_date = (timezone.localtime() - timedelta(days=days))
for i in range(0, children): for i in range(0, children):
child = models.Child.objects.create( self.child = models.Child.objects.create(
first_name=self.faker.first_name(), first_name=self.faker.first_name(),
last_name=self.faker.last_name(), last_name=self.faker.last_name(),
birth_date=birth_date birth_date=birth_date
) )
child.save() self.child.save()
self._add_child_data()
for j in range(days - 1, -1, -1):
date = (timezone.localtime() - timedelta(days=j)).replace(
hour=0, minute=0, second=0)
self._add_child_data(child, date)
if verbosity > 0: if verbosity > 0:
self.stdout.write( self.stdout.write(
self.style.SUCCESS('Successfully added fake data.') self.style.SUCCESS('Successfully added fake data.')
) )
def _add_child_data(self, child, date): @transaction.atomic
now = timezone.localtime() def _add_child_data(self):
"""
Adds fake data for child from child.birth_date to now. The fake data
follows a semi-regular pattern of sleep, feed, change, tummy time,
change, tummy time, sleep, etc.
:returns:
"""
self.time = self.child.birth_date
last_weight_entry_time = self.time
self.weight = uniform(2.0, 5.0)
self._add_weight_entry()
self._add_note_entry()
while self.time < self.time_now:
self._add_sleep_entry()
if choice([True, False]):
self._add_diaperchange_entry()
self._add_feeding_entry()
self._add_diaperchange_entry()
if choice([True, False]):
self._add_tummytime_entry()
if choice([True, False]):
self._add_diaperchange_entry()
self._add_tummytime_entry()
if (self.time - last_weight_entry_time).days > 6:
self._add_weight_entry()
last_weight_entry_time = self.time
for i in (range(0, randint(5, 20))): @transaction.atomic
solid = choice([True, False]) def _add_diaperchange_entry(self):
if solid: """
wet = False Add a Diaper Change entry and advance self.time.
color = choice( :returns:
models.DiaperChange._meta.get_field('color').choices)[0] """
else: solid = choice([True, False, False, False])
wet = True wet = choice([True, False])
color = '' color = ''
if not wet and not solid:
wet = True
time = self.time + timedelta(minutes=randint(1, 60))
time = date + timedelta(minutes=randint(0, 60 * 24)) if time < self.time_now:
if time < now:
models.DiaperChange.objects.create( models.DiaperChange.objects.create(
child=child, child=self.child,
time=time, time=time,
wet=wet, wet=wet,
solid=solid, solid=solid,
color=color color=color
).save() ).save()
self.time = time
last_end = date @transaction.atomic
while last_end < date + timedelta(days=1): def _add_feeding_entry(self):
method = choice(models.Feeding._meta.get_field( """
'method').choices)[0] Add a Feeding entry and advance self.time.
:returns:
"""
method = choice(models.Feeding._meta.get_field('method').choices)[0]
amount = None
if method is 'bottle': if method is 'bottle':
amount = Decimal('%d.%d' % (randint(0, 6), randint(0, 9))) amount = Decimal('%d.%d' % (randint(0, 6), randint(0, 9)))
else: start = self.time + timedelta(minutes=randint(1, 60))
amount = None
start = last_end + timedelta(minutes=randint(0, 60 * 2))
end = start + timedelta(minutes=randint(5, 20)) end = start + timedelta(minutes=randint(5, 20))
if end > now:
break
if end < self.time_now:
models.Feeding.objects.create( models.Feeding.objects.create(
child=child, child=self.child,
start=start, start=start,
end=end, end=end,
type=choice(models.Feeding._meta.get_field('type').choices)[0], type=choice(models.Feeding._meta.get_field('type').choices)[0],
method=method, method=method,
amount=amount amount=amount
).save() ).save()
last_end = end self.time = end
last_end = date @transaction.atomic
def _add_note_entry(self):
"""
Add a Note entry.
:returns:
"""
note = self.faker.sentence()
models.Note.objects.create(child=self.child, note=note).save()
# Adjust last_end if the last sleep entry crossed in to date. @transaction.atomic
last_entry = models.Sleep.objects.filter( def _add_sleep_entry(self):
child=child).order_by('end').last() """
if last_entry: Add a Sleep entry and advance self.time. Between the hours of 18 and 6,
last_entry_end = timezone.localtime(last_entry.end) extend the minimum and maximum sleep duration settings.
if last_entry_end > last_end: :returns:
last_end = last_entry_end """
if self.time.hour < 6 or self.time.hour > 18:
while last_end < date + timedelta(days=1): minutes = randint(60 * 2, 60 * 6)
start = last_end + timedelta(minutes=randint(0, 60 * 2)) else:
if start.date() != date.date(): minutes = randint(30, 60 * 2)
break end = self.time + timedelta(minutes=minutes)
end = start + timedelta(minutes=randint(10, 60 * 3))
if end > now:
break
if end < self.time_now:
models.Sleep.objects.create( models.Sleep.objects.create(
child=child, start=start, end=end).save() child=self.child,
last_end = end start=self.time,
end=end
).save()
self.time = end
last_end = date @transaction.atomic
while last_end < date + timedelta(days=1): def _add_tummytime_entry(self):
"""
Add a Tummy time entry and advance self.time.
:returns:
"""
milestone = ''
if choice([True, False]): if choice([True, False]):
milestone = self.faker.sentence() milestone = self.faker.sentence()
else: start = self.time + timedelta(minutes=randint(1, 60))
milestone = '' end = start + timedelta(minutes=randint(0, 10), seconds=randint(0, 59))
if (end - start).seconds < 20:
start = last_end + timedelta(minutes=randint(0, 60 * 5)) end = start + timedelta(minutes=1, seconds=30)
end = start + timedelta(minutes=randint(1, 10))
if end > now:
break
if end < self.time_now:
models.TummyTime.objects.create( models.TummyTime.objects.create(
child=child, child=self.child,
start=start, start=start,
end=end, end=end,
milestone=milestone milestone=milestone
).save() ).save()
last_end = end self.time = end
last_entry = models.Weight.objects.filter(child=child) \ @transaction.atomic
.order_by('date').last() def _add_weight_entry(self):
if not last_entry: """
weight = uniform(2.0, 5.0) Add a Weight entry. This assumes a weekly interval.
else: :returns:
weight = last_entry.weight + uniform(0, 0.04) """
self.weight += uniform(0.1, 0.3)
models.Weight.objects.create( models.Weight.objects.create(
child=child, child=self.child,
weight=weight, weight=self.weight,
date=date.date() date=self.time.date()
).save()
note = self.faker.sentence()
models.Note.objects.create(
child=child,
note=note,
time=date + timedelta(minutes=randint(0, 60 * 24))
).save() ).save()