Format code with black

This commit is contained in:
Christopher C. Wells 2022-02-09 16:00:30 -08:00
parent bf9bd4dd8a
commit cd946280cb
112 changed files with 8235 additions and 3767 deletions

View File

@ -6,64 +6,59 @@ from core import models
class ChildFieldFilter(filters.FilterSet): class ChildFieldFilter(filters.FilterSet):
class Meta: class Meta:
abstract = True abstract = True
fields = ['child'] fields = ["child"]
class TimeFieldFilter(ChildFieldFilter): class TimeFieldFilter(ChildFieldFilter):
date = filters.DateFilter(field_name='time__date', label='Date') date = filters.DateFilter(field_name="time__date", label="Date")
date_max = filters.DateFilter(field_name='time__date', label='Max. Date', date_max = filters.DateFilter(
lookup_expr='lte') field_name="time__date", label="Max. Date", lookup_expr="lte"
date_min = filters.DateFilter(field_name='time__date', label='Min. Date', )
lookup_expr='gte') date_min = filters.DateFilter(
field_name="time__date", label="Min. Date", lookup_expr="gte"
)
class Meta: class Meta:
abstract = True abstract = True
fields = sorted(ChildFieldFilter.Meta.fields + [ fields = sorted(ChildFieldFilter.Meta.fields + ["date", "date_max", "date_min"])
'date',
'date_max',
'date_min'
])
class StartEndFieldFilter(ChildFieldFilter): class StartEndFieldFilter(ChildFieldFilter):
end = filters.DateFilter(field_name='end__date', label='End Date') end = filters.DateFilter(field_name="end__date", label="End Date")
end_max = filters.DateFilter(field_name='end__date', label='Max. End Date', end_max = filters.DateFilter(
lookup_expr='lte') field_name="end__date", label="Max. End Date", lookup_expr="lte"
end_min = filters.DateFilter(field_name='end__date', label='Min. End Date', )
lookup_expr='gte') end_min = filters.DateFilter(
start = filters.DateFilter(field_name='start__date', label='Start Date') field_name="end__date", label="Min. End Date", lookup_expr="gte"
start_max = filters.DateFilter(field_name='start__date', lookup_expr='lte', )
label='Max. End Date') start = filters.DateFilter(field_name="start__date", label="Start Date")
start_min = filters.DateFilter(field_name='start__date', lookup_expr='gte', start_max = filters.DateFilter(
label='Min. Start Date') field_name="start__date", lookup_expr="lte", label="Max. End Date"
)
start_min = filters.DateFilter(
field_name="start__date", lookup_expr="gte", label="Min. Start Date"
)
class Meta: class Meta:
abstract = True abstract = True
fields = sorted(ChildFieldFilter.Meta.fields + [ fields = sorted(
'end', ChildFieldFilter.Meta.fields
'end_max', + ["end", "end_max", "end_min", "start", "start_max", "start_min"]
'end_min', )
'start',
'start_max',
'start_min'
])
class DiaperChangeFilter(TimeFieldFilter): class DiaperChangeFilter(TimeFieldFilter):
class Meta(TimeFieldFilter.Meta): class Meta(TimeFieldFilter.Meta):
model = models.DiaperChange model = models.DiaperChange
fields = sorted(TimeFieldFilter.Meta.fields + [ fields = sorted(
'wet', TimeFieldFilter.Meta.fields + ["wet", "solid", "color", "amount"]
'solid', )
'color',
'amount'
])
class FeedingFilter(StartEndFieldFilter): class FeedingFilter(StartEndFieldFilter):
class Meta(StartEndFieldFilter.Meta): class Meta(StartEndFieldFilter.Meta):
model = models.Feeding model = models.Feeding
fields = sorted(StartEndFieldFilter.Meta.fields + ['type', 'method']) fields = sorted(StartEndFieldFilter.Meta.fields + ["type", "method"])
class NoteFilter(TimeFieldFilter): class NoteFilter(TimeFieldFilter):
@ -84,7 +79,7 @@ class TemperatureFilter(TimeFieldFilter):
class TimerFilter(StartEndFieldFilter): class TimerFilter(StartEndFieldFilter):
class Meta(StartEndFieldFilter.Meta): class Meta(StartEndFieldFilter.Meta):
model = models.Timer model = models.Timer
fields = sorted(StartEndFieldFilter.Meta.fields + ['active', 'user']) fields = sorted(StartEndFieldFilter.Meta.fields + ["active", "user"])
class TummyTimeFilter(StartEndFieldFilter): class TummyTimeFilter(StartEndFieldFilter):

View File

@ -6,11 +6,12 @@ class APIMetadata(metadata.SimpleMetadata):
""" """
Custom metadata class for OPTIONS responses. Custom metadata class for OPTIONS responses.
""" """
def determine_metadata(self, request, view): def determine_metadata(self, request, view):
data = super(APIMetadata, self).determine_metadata(request, view) data = super(APIMetadata, self).determine_metadata(request, view)
data.pop('description') data.pop("description")
if hasattr(view, 'filterset_fields'): if hasattr(view, "filterset_fields"):
data.update({'filters': view.filterset_fields}) data.update({"filters": view.filterset_fields})
elif hasattr(view, 'filterset_class'): elif hasattr(view, "filterset_class"):
data.update({'filters': view.filterset_class.Meta.fields}) data.update({"filters": view.filterset_class.Meta.fields})
return data return data

View File

@ -11,17 +11,19 @@ class TimerFieldSupportMixin:
""" """
meta = self.metadata_class() meta = self.metadata_class()
data = meta.determine_metadata(request, self) data = meta.determine_metadata(request, self)
post = data.get('actions').get('POST') # type: OrderedDict post = data.get("actions").get("POST") # type: OrderedDict
post['timer'] = OrderedDict({ post["timer"] = OrderedDict(
{
"type": "integer", "type": "integer",
"required": False, "required": False,
"read_only": False, "read_only": False,
"label": "Timer", "label": "Timer",
"details": "ID for an existing Timer, may be used in place of the " "details": "ID for an existing Timer, may be used in place of the "
"`start`, `end`, and/or `child` fields. " "`start`, `end`, and/or `child` fields. ",
}) }
)
details = "Required unless a value is provided in the `timer` field." details = "Required unless a value is provided in the `timer` field."
post['child']['details'] = details post["child"]["details"] = details
post['start']['details'] = details post["start"]["details"] = details
post['end']['details'] = details post["end"]["details"] = details
return Response(data) return Response(data)

View File

@ -4,11 +4,11 @@ from rest_framework.permissions import DjangoModelPermissions
class BabyBuddyDjangoModelPermissions(DjangoModelPermissions): class BabyBuddyDjangoModelPermissions(DjangoModelPermissions):
perms_map = { perms_map = {
'GET': ['%(app_label)s.view_%(model_name)s'], "GET": ["%(app_label)s.view_%(model_name)s"],
'OPTIONS': ['%(app_label)s.add_%(model_name)s'], "OPTIONS": ["%(app_label)s.add_%(model_name)s"],
'HEAD': [], "HEAD": [],
'POST': ['%(app_label)s.add_%(model_name)s'], "POST": ["%(app_label)s.add_%(model_name)s"],
# 'PUT': ['%(app_label)s.change_%(model_name)s'], # 'PUT': ['%(app_label)s.change_%(model_name)s'],
'PATCH': ['%(app_label)s.change_%(model_name)s'], "PATCH": ["%(app_label)s.change_%(model_name)s"],
'DELETE': ['%(app_label)s.delete_%(model_name)s'], "DELETE": ["%(app_label)s.delete_%(model_name)s"],
} }

View File

@ -14,8 +14,8 @@ class CoreModelSerializer(serializers.HyperlinkedModelSerializer):
Provide the child link (used by most core models) and run model clean() Provide the child link (used by most core models) and run model clean()
methods during POST operations. methods during POST operations.
""" """
child = serializers.PrimaryKeyRelatedField(
queryset=models.Child.objects.all()) child = serializers.PrimaryKeyRelatedField(queryset=models.Child.objects.all())
def validate(self, attrs): def validate(self, attrs):
# Ensure that all instance data is available for partial updates to # Ensure that all instance data is available for partial updates to
@ -34,15 +34,19 @@ class CoreModelWithDurationSerializer(CoreModelSerializer):
""" """
Specific serializer base for models with a "start" and "end" field. Specific serializer base for models with a "start" and "end" field.
""" """
child = serializers.PrimaryKeyRelatedField( child = serializers.PrimaryKeyRelatedField(
allow_null=True, allow_empty=True, queryset=models.Child.objects.all(), allow_null=True,
required=False) allow_empty=True,
queryset=models.Child.objects.all(),
required=False,
)
class Meta: class Meta:
abstract = True abstract = True
extra_kwargs = { extra_kwargs = {
'start': {'required': False}, "start": {"required": False},
'end': {'required': False}, "end": {"required": False},
} }
def validate(self, attrs): def validate(self, attrs):
@ -50,30 +54,30 @@ class CoreModelWithDurationSerializer(CoreModelSerializer):
# of "start" and "end" fields as well as "child" if it is set on the # of "start" and "end" fields as well as "child" if it is set on the
# Timer entry. # Timer entry.
timer = None timer = None
if 'timer' in self.initial_data: if "timer" in self.initial_data:
try: try:
timer = models.Timer.objects.get(pk=self.initial_data['timer']) timer = models.Timer.objects.get(pk=self.initial_data["timer"])
except models.Timer.DoesNotExist: except models.Timer.DoesNotExist:
raise ValidationError({'timer': ['Timer does not exist.']}) raise ValidationError({"timer": ["Timer does not exist."]})
if timer.end: if timer.end:
end = timer.end end = timer.end
else: else:
end = timezone.now() end = timezone.now()
if timer.child: if timer.child:
attrs['child'] = timer.child attrs["child"] = timer.child
# Overwrites values provided directly! # Overwrites values provided directly!
attrs['start'] = timer.start attrs["start"] = timer.start
attrs['end'] = end attrs["end"] = end
# The "child", "start", and "end" field should all be set at this # The "child", "start", and "end" field should all be set at this
# point. If one is not, model validation will fail because they are # point. If one is not, model validation will fail because they are
# required fields at the model level. # required fields at the model level.
if not self.partial: if not self.partial:
errors = {} errors = {}
for field in ['child', 'start', 'end']: for field in ["child", "start", "end"]:
if field not in attrs or not attrs[field]: if field not in attrs or not attrs[field]:
errors[field] = 'This field is required.' errors[field] = "This field is required."
if len(errors) > 0: if len(errors) > 0:
raise ValidationError(errors) raise ValidationError(errors)
@ -81,7 +85,7 @@ class CoreModelWithDurationSerializer(CoreModelSerializer):
# Only actually stop the timer if all validation passed. # Only actually stop the timer if all validation passed.
if timer: if timer:
timer.stop(attrs['end']) timer.stop(attrs["end"])
return attrs return attrs
@ -89,76 +93,77 @@ class CoreModelWithDurationSerializer(CoreModelSerializer):
class UserSerializer(serializers.ModelSerializer): class UserSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = User model = User
fields = ('id', 'username') fields = ("id", "username")
class ChildSerializer(serializers.HyperlinkedModelSerializer): class ChildSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
model = models.Child model = models.Child
fields = ('id', 'first_name', 'last_name', 'birth_date', 'slug', fields = ("id", "first_name", "last_name", "birth_date", "slug", "picture")
'picture') lookup_field = "slug"
lookup_field = 'slug'
class DiaperChangeSerializer(CoreModelSerializer): class DiaperChangeSerializer(CoreModelSerializer):
class Meta: class Meta:
model = models.DiaperChange model = models.DiaperChange
fields = ( fields = ("id", "child", "time", "wet", "solid", "color", "amount", "notes")
'id',
'child',
'time',
'wet',
'solid',
'color',
'amount',
'notes'
)
class FeedingSerializer(CoreModelWithDurationSerializer): class FeedingSerializer(CoreModelWithDurationSerializer):
class Meta(CoreModelWithDurationSerializer.Meta): class Meta(CoreModelWithDurationSerializer.Meta):
model = models.Feeding model = models.Feeding
fields = ('id', 'child', 'start', 'end', 'duration', 'type', 'method', fields = (
'amount', 'notes') "id",
"child",
"start",
"end",
"duration",
"type",
"method",
"amount",
"notes",
)
class NoteSerializer(CoreModelSerializer): class NoteSerializer(CoreModelSerializer):
class Meta: class Meta:
model = models.Note model = models.Note
fields = ('id', 'child', 'note', 'time') fields = ("id", "child", "note", "time")
class SleepSerializer(CoreModelWithDurationSerializer): class SleepSerializer(CoreModelWithDurationSerializer):
class Meta(CoreModelWithDurationSerializer.Meta): class Meta(CoreModelWithDurationSerializer.Meta):
model = models.Sleep model = models.Sleep
fields = ('id', 'child', 'start', 'end', 'duration', 'nap', 'notes') fields = ("id", "child", "start", "end", "duration", "nap", "notes")
class TemperatureSerializer(CoreModelSerializer): class TemperatureSerializer(CoreModelSerializer):
class Meta: class Meta:
model = models.Temperature model = models.Temperature
fields = ('id', 'child', 'temperature', 'time', 'notes') fields = ("id", "child", "temperature", "time", "notes")
class TimerSerializer(CoreModelSerializer): class TimerSerializer(CoreModelSerializer):
child = serializers.PrimaryKeyRelatedField( child = serializers.PrimaryKeyRelatedField(
allow_null=True, allow_empty=True, queryset=models.Child.objects.all(), allow_null=True,
required=False) allow_empty=True,
queryset=models.Child.objects.all(),
required=False,
)
user = serializers.PrimaryKeyRelatedField( user = serializers.PrimaryKeyRelatedField(
allow_null=True, allow_empty=True, queryset=User.objects.all(), allow_null=True, allow_empty=True, queryset=User.objects.all(), required=False
required=False) )
class Meta: class Meta:
model = models.Timer model = models.Timer
fields = ('id', 'child', 'name', 'start', 'end', 'duration', 'active', fields = ("id", "child", "name", "start", "end", "duration", "active", "user")
'user')
def validate(self, attrs): def validate(self, attrs):
attrs = super(TimerSerializer, self).validate(attrs) attrs = super(TimerSerializer, self).validate(attrs)
# Set user to current user if no value is provided. # Set user to current user if no value is provided.
if 'user' not in attrs or attrs['user'] is None: if "user" not in attrs or attrs["user"] is None:
attrs['user'] = self.context['request'].user attrs["user"] = self.context["request"].user
return attrs return attrs
@ -166,28 +171,28 @@ class TimerSerializer(CoreModelSerializer):
class TummyTimeSerializer(CoreModelWithDurationSerializer): class TummyTimeSerializer(CoreModelWithDurationSerializer):
class Meta(CoreModelWithDurationSerializer.Meta): class Meta(CoreModelWithDurationSerializer.Meta):
model = models.TummyTime model = models.TummyTime
fields = ('id', 'child', 'start', 'end', 'duration', 'milestone') fields = ("id", "child", "start", "end", "duration", "milestone")
class WeightSerializer(CoreModelSerializer): class WeightSerializer(CoreModelSerializer):
class Meta: class Meta:
model = models.Weight model = models.Weight
fields = ('id', 'child', 'weight', 'date', 'notes') fields = ("id", "child", "weight", "date", "notes")
class HeightSerializer(CoreModelSerializer): class HeightSerializer(CoreModelSerializer):
class Meta: class Meta:
model = models.Height model = models.Height
fields = ('id', 'child', 'height', 'date', 'notes') fields = ("id", "child", "height", "date", "notes")
class HeadCircumferenceSerializer(CoreModelSerializer): class HeadCircumferenceSerializer(CoreModelSerializer):
class Meta: class Meta:
model = models.HeadCircumference model = models.HeadCircumference
fields = ('id', 'child', 'head_circumference', 'date', 'notes') fields = ("id", "child", "head_circumference", "date", "notes")
class BMISerializer(CoreModelSerializer): class BMISerializer(CoreModelSerializer):
class Meta: class Meta:
model = models.BMI model = models.BMI
fields = ('id', 'child', 'bmi', 'date', 'notes') fields = ("id", "child", "bmi", "date", "notes")

View File

@ -10,25 +10,25 @@ from core import models
class TestBase: class TestBase:
class BabyBuddyAPITestCaseBase(APITestCase): class BabyBuddyAPITestCaseBase(APITestCase):
fixtures = ['tests.json'] fixtures = ["tests.json"]
model = None model = None
endpoint = None endpoint = None
delete_id = 1 delete_id = 1
timer_test_data = {} timer_test_data = {}
def setUp(self): def setUp(self):
self.client.login(username='admin', password='admin') self.client.login(username="admin", password="admin")
def test_options(self): def test_options(self):
response = self.client.options(self.endpoint) response = self.client.options(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['name'], '{} List'.format( self.assertEqual(
self.model._meta.verbose_name)) response.data["name"], "{} List".format(self.model._meta.verbose_name)
)
def test_delete(self): def test_delete(self):
endpoint = '{}{}/'.format(self.endpoint, self.delete_id) endpoint = "{}{}/".format(self.endpoint, self.delete_id)
response = self.client.get(endpoint) response = self.client.get(endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.delete(endpoint) response = self.client.delete(endpoint)
@ -40,24 +40,26 @@ class TestBase:
user = User.objects.first() user = User.objects.first()
start = timezone.now() - timezone.timedelta(minutes=10) start = timezone.now() - timezone.timedelta(minutes=10)
timer = models.Timer.objects.create(user=user, start=start) timer = models.Timer.objects.create(user=user, start=start)
self.timer_test_data['timer'] = timer.id self.timer_test_data["timer"] = timer.id
if 'child' in self.timer_test_data: if "child" in self.timer_test_data:
del self.timer_test_data['child'] del self.timer_test_data["child"]
response = self.client.post( response = self.client.post(
self.endpoint, self.timer_test_data, format='json') self.endpoint, self.timer_test_data, format="json"
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
timer.refresh_from_db() timer.refresh_from_db()
self.assertTrue(timer.active) self.assertTrue(timer.active)
child = models.Child.objects.first() child = models.Child.objects.first()
self.timer_test_data['child'] = child.id self.timer_test_data["child"] = child.id
response = self.client.post( response = self.client.post(
self.endpoint, self.timer_test_data, format='json') self.endpoint, self.timer_test_data, format="json"
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
timer.refresh_from_db() timer.refresh_from_db()
self.assertFalse(timer.active) self.assertFalse(timer.active)
obj = self.model.objects.get(pk=response.data['id']) obj = self.model.objects.get(pk=response.data["id"])
self.assertEqual(obj.start, start) self.assertEqual(obj.start, start)
self.assertEqual(obj.end, timer.end) self.assertEqual(obj.end, timer.end)
@ -67,339 +69,374 @@ class TestBase:
user = User.objects.first() user = User.objects.first()
child = models.Child.objects.first() child = models.Child.objects.first()
start = timezone.now() - timezone.timedelta(minutes=10) start = timezone.now() - timezone.timedelta(minutes=10)
timer = models.Timer.objects.create( timer = models.Timer.objects.create(user=user, child=child, start=start)
user=user, child=child, start=start) self.timer_test_data["timer"] = timer.id
self.timer_test_data['timer'] = timer.id
response = self.client.post( response = self.client.post(
self.endpoint, self.timer_test_data, format='json') self.endpoint, self.timer_test_data, format="json"
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
timer.refresh_from_db() timer.refresh_from_db()
self.assertFalse(timer.active) self.assertFalse(timer.active)
obj = self.model.objects.get(pk=response.data['id']) obj = self.model.objects.get(pk=response.data["id"])
self.assertEqual(obj.child, timer.child) self.assertEqual(obj.child, timer.child)
self.assertEqual(obj.start, start) self.assertEqual(obj.start, start)
self.assertEqual(obj.end, timer.end) self.assertEqual(obj.end, timer.end)
class ChildAPITestCase(TestBase.BabyBuddyAPITestCaseBase): class ChildAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
endpoint = reverse('api:child-list') endpoint = reverse("api:child-list")
model = models.Child model = models.Child
delete_id = 'fake-child' delete_id = "fake-child"
def test_get(self): def test_get(self):
response = self.client.get(self.endpoint) response = self.client.get(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'][0], { self.assertEqual(
'id': 1, response.data["results"][0],
'first_name': 'Fake', {
'last_name': 'Child', "id": 1,
'birth_date': '2017-11-11', "first_name": "Fake",
'slug': 'fake-child', "last_name": "Child",
'picture': None "birth_date": "2017-11-11",
}) "slug": "fake-child",
"picture": None,
},
)
def test_post(self): def test_post(self):
data = { data = {"first_name": "Test", "last_name": "Child", "birth_date": "2017-11-12"}
'first_name': 'Test', response = self.client.post(self.endpoint, data, format="json")
'last_name': 'Child',
'birth_date': '2017-11-12'
}
response = self.client.post(self.endpoint, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
obj = models.Child.objects.get(pk=response.data['id']) obj = models.Child.objects.get(pk=response.data["id"])
self.assertEqual(obj.first_name, data['first_name']) self.assertEqual(obj.first_name, data["first_name"])
def test_patch(self): def test_patch(self):
endpoint = '{}{}/'.format(self.endpoint, 'fake-child') endpoint = "{}{}/".format(self.endpoint, "fake-child")
response = self.client.get(endpoint) response = self.client.get(endpoint)
entry = response.data entry = response.data
entry['first_name'] = 'New' entry["first_name"] = "New"
entry['last_name'] = 'Name' entry["last_name"] = "Name"
response = self.client.patch(endpoint, { response = self.client.patch(
'first_name': entry['first_name'], endpoint,
'last_name': entry['last_name'], {
}) "first_name": entry["first_name"],
"last_name": entry["last_name"],
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
# The slug we be updated by the name change. # The slug we be updated by the name change.
entry['slug'] = 'new-name' entry["slug"] = "new-name"
self.assertEqual(response.data, entry) self.assertEqual(response.data, entry)
class DiaperChangeAPITestCase(TestBase.BabyBuddyAPITestCaseBase): class DiaperChangeAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
endpoint = reverse('api:diaperchange-list') endpoint = reverse("api:diaperchange-list")
model = models.DiaperChange model = models.DiaperChange
delete_id = 3 delete_id = 3
def test_get(self): def test_get(self):
response = self.client.get(self.endpoint) response = self.client.get(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'][0], { self.assertEqual(
'id': 3, response.data["results"][0],
'child': 1, {
'time': '2017-11-18T14:00:00-05:00', "id": 3,
'wet': True, "child": 1,
'solid': False, "time": "2017-11-18T14:00:00-05:00",
'color': '', "wet": True,
'amount': 2.25, "solid": False,
'notes': 'stinky' "color": "",
}) "amount": 2.25,
"notes": "stinky",
},
)
def test_post(self): def test_post(self):
data = { data = {
'child': 1, "child": 1,
'time': '2017-11-18T12:00:00-05:00', "time": "2017-11-18T12:00:00-05:00",
'wet': True, "wet": True,
'solid': True, "solid": True,
'color': 'brown', "color": "brown",
'amount': 1.25, "amount": 1.25,
'notes': 'seedy' "notes": "seedy",
} }
response = self.client.post(self.endpoint, data, format='json') response = self.client.post(self.endpoint, data, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
obj = models.DiaperChange.objects.get(pk=response.data['id']) obj = models.DiaperChange.objects.get(pk=response.data["id"])
self.assertTrue(obj.wet) self.assertTrue(obj.wet)
self.assertTrue(obj.solid) self.assertTrue(obj.solid)
self.assertEqual(obj.color, data['color']) self.assertEqual(obj.color, data["color"])
self.assertEqual(obj.amount, data['amount']) self.assertEqual(obj.amount, data["amount"])
self.assertEqual(obj.notes, data['notes']) self.assertEqual(obj.notes, data["notes"])
def test_patch(self): def test_patch(self):
endpoint = '{}{}/'.format(self.endpoint, 3) endpoint = "{}{}/".format(self.endpoint, 3)
response = self.client.get(endpoint) response = self.client.get(endpoint)
entry = response.data entry = response.data
entry['wet'] = False entry["wet"] = False
entry['solid'] = True entry["solid"] = True
response = self.client.patch(endpoint, { response = self.client.patch(
'wet': entry['wet'], endpoint,
'solid': entry['solid'], {
}) "wet": entry["wet"],
"solid": entry["solid"],
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, entry) self.assertEqual(response.data, entry)
class FeedingAPITestCase(TestBase.BabyBuddyAPITestCaseBase): class FeedingAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
endpoint = reverse('api:feeding-list') endpoint = reverse("api:feeding-list")
model = models.Feeding model = models.Feeding
timer_test_data = {'type': 'breast milk', 'method': 'left breast'} timer_test_data = {"type": "breast milk", "method": "left breast"}
def test_get(self): def test_get(self):
response = self.client.get(self.endpoint) response = self.client.get(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'][0], { self.assertEqual(
'id': 3, response.data["results"][0],
'child': 1, {
'start': '2017-11-18T14:00:00-05:00', "id": 3,
'end': '2017-11-18T14:15:00-05:00', "child": 1,
'duration': '00:15:00', "start": "2017-11-18T14:00:00-05:00",
'type': 'formula', "end": "2017-11-18T14:15:00-05:00",
'method': 'bottle', "duration": "00:15:00",
'amount': 2.5, "type": "formula",
'notes': 'forgot vitamins :(' "method": "bottle",
}) "amount": 2.5,
"notes": "forgot vitamins :(",
},
)
def test_post(self): def test_post(self):
data = { data = {
'child': 1, "child": 1,
'start': '2017-11-19T14:00:00-05:00', "start": "2017-11-19T14:00:00-05:00",
'end': '2017-11-19T14:15:00-05:00', "end": "2017-11-19T14:15:00-05:00",
'type': 'breast milk', "type": "breast milk",
'method': 'left breast', "method": "left breast",
'notes': 'with vitamins' "notes": "with vitamins",
} }
response = self.client.post(self.endpoint, data, format='json') response = self.client.post(self.endpoint, data, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
obj = models.Feeding.objects.get(pk=response.data['id']) obj = models.Feeding.objects.get(pk=response.data["id"])
self.assertEqual(obj.type, data['type']) self.assertEqual(obj.type, data["type"])
self.assertEqual(obj.notes, data['notes']) self.assertEqual(obj.notes, data["notes"])
def test_patch(self): def test_patch(self):
endpoint = '{}{}/'.format(self.endpoint, 3) endpoint = "{}{}/".format(self.endpoint, 3)
response = self.client.get(endpoint) response = self.client.get(endpoint)
entry = response.data entry = response.data
entry['type'] = 'breast milk' entry["type"] = "breast milk"
entry['method'] = 'left breast' entry["method"] = "left breast"
entry['amount'] = 0 entry["amount"] = 0
response = self.client.patch(endpoint, { response = self.client.patch(
'type': entry['type'], endpoint,
'method': entry['method'], {
'amount': entry['amount'], "type": entry["type"],
}) "method": entry["method"],
"amount": entry["amount"],
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, entry) self.assertEqual(response.data, entry)
class NoteAPITestCase(TestBase.BabyBuddyAPITestCaseBase): class NoteAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
endpoint = reverse('api:note-list') endpoint = reverse("api:note-list")
model = models.Note model = models.Note
def test_get(self): def test_get(self):
response = self.client.get(self.endpoint) response = self.client.get(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'][0], { self.assertEqual(
'id': 1, response.data["results"][0],
'child': 1, {
'note': 'Fake note.', "id": 1,
'time': '2017-11-17T22:45:00-05:00' "child": 1,
}) "note": "Fake note.",
"time": "2017-11-17T22:45:00-05:00",
},
)
def test_post(self): def test_post(self):
data = { data = {
'child': 1, "child": 1,
'note': 'New fake note.', "note": "New fake note.",
'time': '2017-11-18T22:45:00-05:00' "time": "2017-11-18T22:45:00-05:00",
} }
response = self.client.post(self.endpoint, data, format='json') response = self.client.post(self.endpoint, data, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
obj = models.Note.objects.get(pk=response.data['id']) obj = models.Note.objects.get(pk=response.data["id"])
self.assertEqual(obj.note, data['note']) self.assertEqual(obj.note, data["note"])
def test_patch(self): def test_patch(self):
endpoint = '{}{}/'.format(self.endpoint, 1) endpoint = "{}{}/".format(self.endpoint, 1)
response = self.client.get(endpoint) response = self.client.get(endpoint)
entry = response.data entry = response.data
entry['note'] = 'Updated note text.' entry["note"] = "Updated note text."
response = self.client.patch(endpoint, { response = self.client.patch(
'note': entry['note'], endpoint,
}) {
"note": entry["note"],
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
# The time of entry will always update automatically, so only check the # The time of entry will always update automatically, so only check the
# new value. # new value.
self.assertEqual(response.data['note'], entry['note']) self.assertEqual(response.data["note"], entry["note"])
class SleepAPITestCase(TestBase.BabyBuddyAPITestCaseBase): class SleepAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
endpoint = reverse('api:sleep-list') endpoint = reverse("api:sleep-list")
model = models.Sleep model = models.Sleep
timer_test_data = {'child': 1} timer_test_data = {"child": 1}
def test_get(self): def test_get(self):
response = self.client.get(self.endpoint) response = self.client.get(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'][0], { self.assertEqual(
'id': 4, response.data["results"][0],
'child': 1, {
'start': '2017-11-18T19:00:00-05:00', "id": 4,
'end': '2017-11-18T23:00:00-05:00', "child": 1,
'duration': '04:00:00', "start": "2017-11-18T19:00:00-05:00",
'nap': False, "end": "2017-11-18T23:00:00-05:00",
'notes': 'lots of squirming' "duration": "04:00:00",
}) "nap": False,
"notes": "lots of squirming",
},
)
def test_post(self): def test_post(self):
data = { data = {
'child': 1, "child": 1,
'start': '2017-11-21T19:30:00-05:00', "start": "2017-11-21T19:30:00-05:00",
'end': '2017-11-21T23:00:00-05:00', "end": "2017-11-21T23:00:00-05:00",
'notes': 'used new swaddle' "notes": "used new swaddle",
} }
response = self.client.post(self.endpoint, data, format='json') response = self.client.post(self.endpoint, data, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
obj = models.Sleep.objects.get(pk=response.data['id']) obj = models.Sleep.objects.get(pk=response.data["id"])
self.assertEqual(str(obj.duration), '3:30:00') self.assertEqual(str(obj.duration), "3:30:00")
self.assertEqual(obj.notes, data['notes']) self.assertEqual(obj.notes, data["notes"])
def test_patch(self): def test_patch(self):
endpoint = '{}{}/'.format(self.endpoint, 4) endpoint = "{}{}/".format(self.endpoint, 4)
response = self.client.get(endpoint) response = self.client.get(endpoint)
entry = response.data entry = response.data
entry['end'] = '2017-11-18T23:30:00-05:00' entry["end"] = "2017-11-18T23:30:00-05:00"
response = self.client.patch(endpoint, { response = self.client.patch(
'end': entry['end'], endpoint,
}) {
"end": entry["end"],
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
# The duration of entry will always update automatically, so only check # The duration of entry will always update automatically, so only check
# the new value. # the new value.
self.assertEqual(response.data['end'], entry['end']) self.assertEqual(response.data["end"], entry["end"])
class TemperatureAPITestCase(TestBase.BabyBuddyAPITestCaseBase): class TemperatureAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
endpoint = reverse('api:temperature-list') endpoint = reverse("api:temperature-list")
model = models.Temperature model = models.Temperature
def test_get(self): def test_get(self):
response = self.client.get(self.endpoint) response = self.client.get(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'][0], { self.assertEqual(
'id': 1, response.data["results"][0],
'child': 1, {
'temperature': 98.6, "id": 1,
'time': '2017-11-17T12:52:00-05:00', "child": 1,
'notes': 'tympanic' "temperature": 98.6,
}) "time": "2017-11-17T12:52:00-05:00",
"notes": "tympanic",
},
)
def test_post(self): def test_post(self):
data = { data = {
'child': 1, "child": 1,
'temperature': '100.1', "temperature": "100.1",
'time': '2017-11-20T22:52:00-05:00', "time": "2017-11-20T22:52:00-05:00",
'notes': 'rectal' "notes": "rectal",
} }
response = self.client.post(self.endpoint, data, format='json') response = self.client.post(self.endpoint, data, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
obj = models.Temperature.objects.get(pk=response.data['id']) obj = models.Temperature.objects.get(pk=response.data["id"])
self.assertEqual(str(obj.temperature), data['temperature']) self.assertEqual(str(obj.temperature), data["temperature"])
self.assertEqual(obj.notes, data['notes']) self.assertEqual(obj.notes, data["notes"])
def test_patch(self): def test_patch(self):
endpoint = '{}{}/'.format(self.endpoint, 1) endpoint = "{}{}/".format(self.endpoint, 1)
response = self.client.get(endpoint) response = self.client.get(endpoint)
entry = response.data entry = response.data
entry['temperature'] = 99 entry["temperature"] = 99
response = self.client.patch(endpoint, { response = self.client.patch(
'temperature': entry['temperature'], endpoint,
}) {
"temperature": entry["temperature"],
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, entry) self.assertEqual(response.data, entry)
class TimerAPITestCase(TestBase.BabyBuddyAPITestCaseBase): class TimerAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
endpoint = reverse('api:timer-list') endpoint = reverse("api:timer-list")
model = models.Timer model = models.Timer
def test_get(self): def test_get(self):
response = self.client.get(self.endpoint) response = self.client.get(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'][0], { self.assertEqual(
'id': 1, response.data["results"][0],
'child': None, {
'name': 'Fake timer', "id": 1,
'start': '2017-11-17T23:30:00-05:00', "child": None,
'end': '2017-11-18T00:30:00-05:00', "name": "Fake timer",
'duration': '01:00:00', "start": "2017-11-17T23:30:00-05:00",
'active': False, "end": "2017-11-18T00:30:00-05:00",
'user': 1 "duration": "01:00:00",
}) "active": False,
"user": 1,
},
)
def test_post(self): def test_post(self):
data = { data = {"name": "New fake timer", "user": 1}
'name': 'New fake timer', response = self.client.post(self.endpoint, data, format="json")
'user': 1
}
response = self.client.post(self.endpoint, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
obj = models.Timer.objects.get(pk=response.data['id']) obj = models.Timer.objects.get(pk=response.data["id"])
self.assertEqual(obj.name, data['name']) self.assertEqual(obj.name, data["name"])
def test_post_default_user(self): def test_post_default_user(self):
user = User.objects.first() user = User.objects.first()
response = self.client.post(self.endpoint) response = self.client.post(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
obj = models.Timer.objects.get(pk=response.data['id']) obj = models.Timer.objects.get(pk=response.data["id"])
self.assertEqual(obj.user, user) self.assertEqual(obj.user, user)
def test_patch(self): def test_patch(self):
endpoint = '{}{}/'.format(self.endpoint, 1) endpoint = "{}{}/".format(self.endpoint, 1)
response = self.client.get(endpoint) response = self.client.get(endpoint)
entry = response.data entry = response.data
entry['name'] = 'New Timer Name' entry["name"] = "New Timer Name"
response = self.client.patch(endpoint, { response = self.client.patch(
'name': entry['name'], endpoint,
}) {
"name": entry["name"],
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, entry) self.assertEqual(response.data, entry)
def test_start_stop_timer(self): def test_start_stop_timer(self):
endpoint = '{}{}/'.format(self.endpoint, 1) endpoint = "{}{}/".format(self.endpoint, 1)
response = self.client.get(endpoint) response = self.client.get(endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertFalse(response.data["active"]) self.assertFalse(response.data["active"])
@ -424,81 +461,93 @@ class TimerAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
class TummyTimeAPITestCase(TestBase.BabyBuddyAPITestCaseBase): class TummyTimeAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
endpoint = reverse('api:tummytime-list') endpoint = reverse("api:tummytime-list")
model = models.TummyTime model = models.TummyTime
timer_test_data = {'milestone': 'Timer test'} timer_test_data = {"milestone": "Timer test"}
def test_get(self): def test_get(self):
response = self.client.get(self.endpoint) response = self.client.get(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'][0], { self.assertEqual(
'id': 3, response.data["results"][0],
'child': 1, {
'start': '2017-11-18T15:30:00-05:00', "id": 3,
'end': '2017-11-18T15:30:45-05:00', "child": 1,
'duration': '00:00:45', "start": "2017-11-18T15:30:00-05:00",
'milestone': '' "end": "2017-11-18T15:30:45-05:00",
}) "duration": "00:00:45",
"milestone": "",
},
)
def test_post(self): def test_post(self):
data = { data = {
'child': 1, "child": 1,
'start': '2017-11-18T12:30:00-05:00', "start": "2017-11-18T12:30:00-05:00",
'end': '2017-11-18T12:35:30-05:00', "end": "2017-11-18T12:35:30-05:00",
'milestone': 'Rolled over.' "milestone": "Rolled over.",
} }
response = self.client.post(self.endpoint, data, format='json') response = self.client.post(self.endpoint, data, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
obj = models.TummyTime.objects.get(pk=response.data['id']) obj = models.TummyTime.objects.get(pk=response.data["id"])
self.assertEqual(str(obj.duration), '0:05:30') self.assertEqual(str(obj.duration), "0:05:30")
def test_patch(self): def test_patch(self):
endpoint = '{}{}/'.format(self.endpoint, 3) endpoint = "{}{}/".format(self.endpoint, 3)
response = self.client.get(endpoint) response = self.client.get(endpoint)
entry = response.data entry = response.data
entry['milestone'] = 'Switched sides!' entry["milestone"] = "Switched sides!"
response = self.client.patch(endpoint, { response = self.client.patch(
'milestone': entry['milestone'], endpoint,
}) {
"milestone": entry["milestone"],
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, entry) self.assertEqual(response.data, entry)
class WeightAPITestCase(TestBase.BabyBuddyAPITestCaseBase): class WeightAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
endpoint = reverse('api:weight-list') endpoint = reverse("api:weight-list")
model = models.Weight model = models.Weight
def test_get(self): def test_get(self):
response = self.client.get(self.endpoint) response = self.client.get(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'][0], { self.assertEqual(
'id': 2, response.data["results"][0],
'child': 1, {
'weight': 9.5, "id": 2,
'date': '2017-11-18', "child": 1,
'notes': 'before feed' "weight": 9.5,
}) "date": "2017-11-18",
"notes": "before feed",
},
)
def test_post(self): def test_post(self):
data = { data = {
'child': 1, "child": 1,
'weight': '9.75', "weight": "9.75",
'date': '2017-11-20', "date": "2017-11-20",
'notes': 'after feed' "notes": "after feed",
} }
response = self.client.post(self.endpoint, data, format='json') response = self.client.post(self.endpoint, data, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
obj = models.Weight.objects.get(pk=response.data['id']) obj = models.Weight.objects.get(pk=response.data["id"])
self.assertEqual(str(obj.weight), data['weight']) self.assertEqual(str(obj.weight), data["weight"])
self.assertEqual(str(obj.notes), data['notes']) self.assertEqual(str(obj.notes), data["notes"])
def test_patch(self): def test_patch(self):
endpoint = '{}{}/'.format(self.endpoint, 2) endpoint = "{}{}/".format(self.endpoint, 2)
response = self.client.get(endpoint) response = self.client.get(endpoint)
entry = response.data entry = response.data
entry['weight'] = 8.25 entry["weight"] = 8.25
response = self.client.patch(endpoint, { response = self.client.patch(
'weight': entry['weight'], endpoint,
}) {
"weight": entry["weight"],
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, entry) self.assertEqual(response.data, entry)

View File

@ -6,30 +6,31 @@ from rest_framework.schemas import get_schema_view
from . import views from . import views
router = routers.DefaultRouter() router = routers.DefaultRouter()
router.register(r'children', views.ChildViewSet) router.register(r"children", views.ChildViewSet)
router.register(r'changes', views.DiaperChangeViewSet) router.register(r"changes", views.DiaperChangeViewSet)
router.register(r'feedings', views.FeedingViewSet) router.register(r"feedings", views.FeedingViewSet)
router.register(r'notes', views.NoteViewSet) router.register(r"notes", views.NoteViewSet)
router.register(r'sleep', views.SleepViewSet) router.register(r"sleep", views.SleepViewSet)
router.register(r'temperature', views.TemperatureViewSet) router.register(r"temperature", views.TemperatureViewSet)
router.register(r'timers', views.TimerViewSet) router.register(r"timers", views.TimerViewSet)
router.register(r'tummy-times', views.TummyTimeViewSet) router.register(r"tummy-times", views.TummyTimeViewSet)
router.register(r'weight', views.WeightViewSet) router.register(r"weight", views.WeightViewSet)
router.register(r'height', views.HeightViewSet) router.register(r"height", views.HeightViewSet)
router.register(r'head-circumference', views.HeadCircumferenceViewSet) router.register(r"head-circumference", views.HeadCircumferenceViewSet)
router.register(r'bmi', views.BMIViewSet) router.register(r"bmi", views.BMIViewSet)
app_name = 'api' app_name = "api"
urlpatterns = [ urlpatterns = [
path('api/', include(router.urls)), path("api/", include(router.urls)),
path('api/auth/', include( path("api/auth/", include("rest_framework.urls", namespace="rest_framework")),
'rest_framework.urls', path(
namespace='rest_framework' "api/schema",
)), get_schema_view(
path('api/schema', get_schema_view( title="Baby Buddy API",
title='Baby Buddy API',
version=1, version=1,
description='API documentation for the Baby Buddy application' description="API documentation for the Baby Buddy application",
), name='openapi-schema'), ),
name="openapi-schema",
),
] ]

View File

@ -12,8 +12,8 @@ from .mixins import TimerFieldSupportMixin
class ChildViewSet(viewsets.ModelViewSet): class ChildViewSet(viewsets.ModelViewSet):
queryset = models.Child.objects.all() queryset = models.Child.objects.all()
serializer_class = serializers.ChildSerializer serializer_class = serializers.ChildSerializer
lookup_field = 'slug' lookup_field = "slug"
filterset_fields = ('first_name', 'last_name', 'slug', 'birth_date') filterset_fields = ("first_name", "last_name", "slug", "birth_date")
class DiaperChangeViewSet(viewsets.ModelViewSet): class DiaperChangeViewSet(viewsets.ModelViewSet):
@ -51,13 +51,13 @@ class TimerViewSet(viewsets.ModelViewSet):
serializer_class = serializers.TimerSerializer serializer_class = serializers.TimerSerializer
filterset_class = filters.TimerFilter filterset_class = filters.TimerFilter
@action(detail=True, methods=['patch']) @action(detail=True, methods=["patch"])
def stop(self, request, pk=None): def stop(self, request, pk=None):
timer = self.get_object() timer = self.get_object()
timer.stop() timer.stop()
return Response(self.serializer_class(timer).data) return Response(self.serializer_class(timer).data)
@action(detail=True, methods=['patch']) @action(detail=True, methods=["patch"])
def restart(self, request, pk=None): def restart(self, request, pk=None):
timer = self.get_object() timer = self.get_object()
timer.restart() timer.restart()
@ -73,22 +73,22 @@ class TummyTimeViewSet(TimerFieldSupportMixin, viewsets.ModelViewSet):
class WeightViewSet(viewsets.ModelViewSet): class WeightViewSet(viewsets.ModelViewSet):
queryset = models.Weight.objects.all() queryset = models.Weight.objects.all()
serializer_class = serializers.WeightSerializer serializer_class = serializers.WeightSerializer
filterset_fields = ('child', 'date') filterset_fields = ("child", "date")
class HeightViewSet(viewsets.ModelViewSet): class HeightViewSet(viewsets.ModelViewSet):
queryset = models.Height.objects.all() queryset = models.Height.objects.all()
serializer_class = serializers.HeightSerializer serializer_class = serializers.HeightSerializer
filterset_fields = ('child', 'date') filterset_fields = ("child", "date")
class HeadCircumferenceViewSet(viewsets.ModelViewSet): class HeadCircumferenceViewSet(viewsets.ModelViewSet):
queryset = models.HeadCircumference.objects.all() queryset = models.HeadCircumference.objects.all()
serializer_class = serializers.HeadCircumferenceSerializer serializer_class = serializers.HeadCircumferenceSerializer
filterset_fields = ('child', 'date') filterset_fields = ("child", "date")
class BMIViewSet(viewsets.ModelViewSet): class BMIViewSet(viewsets.ModelViewSet):
queryset = models.BMI.objects.all() queryset = models.BMI.objects.all()
serializer_class = serializers.BMISerializer serializer_class = serializers.BMISerializer
filterset_fields = ('child', 'date') filterset_fields = ("child", "date")

View File

@ -45,10 +45,10 @@
'----------------' '----------------' '----------------' '----------------' '----------------' '----------------' '----------------' '----------------'
""" # noqa """ # noqa
__title__ = 'Baby Buddy' __title__ = "Baby Buddy"
__version__ = '1.9.3' __version__ = "1.9.3"
__license__ = 'BSD 2-Clause' __license__ = "BSD 2-Clause"
VERSION = __version__ VERSION = __version__
default_app_config = 'babybuddy.apps.BabyBuddyConfig' default_app_config = "babybuddy.apps.BabyBuddyConfig"

View File

@ -9,16 +9,20 @@ from babybuddy import models
class SettingsInline(admin.StackedInline): class SettingsInline(admin.StackedInline):
model = models.Settings model = models.Settings
verbose_name = _('Settings') verbose_name = _("Settings")
verbose_name_plural = _('Settings') verbose_name_plural = _("Settings")
can_delete = False can_delete = False
fieldsets = ( fieldsets = (
(_('Dashboard'), { (
'fields': ( _("Dashboard"),
'dashboard_refresh_rate', {
'dashboard_hide_empty', "fields": (
'dashboard_hide_age') "dashboard_refresh_rate",
}), "dashboard_hide_empty",
"dashboard_hide_age",
)
},
),
) )

View File

@ -7,12 +7,12 @@ from babybuddy import VERSION
class BabyBuddyConfig(AppConfig): class BabyBuddyConfig(AppConfig):
name = 'babybuddy' name = "babybuddy"
verbose_name = 'Baby Buddy' verbose_name = "Baby Buddy"
version = VERSION version = VERSION
version_string = VERSION version_string = VERSION
def ready(self): def ready(self):
if os.path.isfile('.git/refs/heads/master'): if os.path.isfile(".git/refs/heads/master"):
commit = open('.git/refs/heads/master').read() commit = open(".git/refs/heads/master").read()
self.version_string += ' ({})'.format(commit[0:7]) self.version_string += " ({})".format(commit[0:7])

View File

@ -9,8 +9,14 @@ from .models import Settings
class UserAddForm(UserCreationForm): class UserAddForm(UserCreationForm):
class Meta: class Meta:
model = User model = User
fields = ['username', 'first_name', 'last_name', 'email', fields = [
'is_staff', 'is_active'] "username",
"first_name",
"last_name",
"email",
"is_staff",
"is_active",
]
def save(self, commit=True): def save(self, commit=True):
user = super(UserAddForm, self).save(commit=False) user = super(UserAddForm, self).save(commit=False)
@ -23,28 +29,34 @@ class UserAddForm(UserCreationForm):
class UserUpdateForm(forms.ModelForm): class UserUpdateForm(forms.ModelForm):
class Meta: class Meta:
model = User model = User
fields = ['username', 'first_name', 'last_name', 'email', fields = [
'is_staff', 'is_active'] "username",
"first_name",
"last_name",
"email",
"is_staff",
"is_active",
]
class UserForm(forms.ModelForm): class UserForm(forms.ModelForm):
class Meta: class Meta:
model = User model = User
fields = ['first_name', 'last_name', 'email'] fields = ["first_name", "last_name", "email"]
class UserPasswordForm(PasswordChangeForm): class UserPasswordForm(PasswordChangeForm):
class Meta: class Meta:
fields = ['old_password', 'new_password1', 'new_password2'] fields = ["old_password", "new_password1", "new_password2"]
class UserSettingsForm(forms.ModelForm): class UserSettingsForm(forms.ModelForm):
class Meta: class Meta:
model = Settings model = Settings
fields = [ fields = [
'dashboard_refresh_rate', "dashboard_refresh_rate",
'dashboard_hide_empty', "dashboard_hide_empty",
'dashboard_hide_age', "dashboard_hide_age",
'language', "language",
'timezone' "timezone",
] ]

View File

@ -13,7 +13,7 @@ from core import models
class Command(BaseCommand): class Command(BaseCommand):
help = 'Generates fake children and related entries.' help = "Generates fake children and related entries."
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Command, self).__init__(*args, **kwargs) super(Command, self).__init__(*args, **kwargs)
@ -25,37 +25,35 @@ class Command(BaseCommand):
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument( parser.add_argument(
'--children', "--children",
dest='children', dest="children",
default=1, default=1,
help='The number of fake children to create.' help="The number of fake children to create.",
) )
parser.add_argument( parser.add_argument(
'--days', "--days",
dest='days', dest="days",
default=31, default=31,
help='How many days of fake entries to create.' help="How many days of fake entries to create.",
) )
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
verbosity = int(kwargs['verbosity']) verbosity = int(kwargs["verbosity"])
children = int(kwargs['children']) or 1 children = int(kwargs["children"]) or 1
days = int(kwargs['days']) or 31 days = int(kwargs["days"]) or 31
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):
self.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,
) )
self.child.save() self.child.save()
self._add_child_data() self._add_child_data()
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.')
)
@transaction.atomic @transaction.atomic
def _add_child_data(self): def _add_child_data(self):
@ -121,18 +119,17 @@ class Command(BaseCommand):
""" """
solid = choice([True, False, False, False]) solid = choice([True, False, False, False])
wet = choice([True, False]) wet = choice([True, False])
color = '' color = ""
if solid: if solid:
color = choice( color = choice(models.DiaperChange._meta.get_field("color").choices)[0]
models.DiaperChange._meta.get_field('color').choices)[0]
if not wet and not solid: if not wet and not solid:
wet = True wet = True
amount = Decimal('%d.%d' % (randint(0, 6), randint(1, 9))) amount = Decimal("%d.%d" % (randint(0, 6), randint(1, 9)))
time = self.time + timedelta(minutes=randint(1, 60)) time = self.time + timedelta(minutes=randint(1, 60))
notes = '' notes = ""
if choice([True, False, False, False]): if choice([True, False, False, False]):
notes = ' '.join(self.faker.sentences(randint(1, 5))) notes = " ".join(self.faker.sentences(randint(1, 5)))
if time < self.time_now: if time < self.time_now:
models.DiaperChange.objects.create( models.DiaperChange.objects.create(
@ -142,7 +139,7 @@ class Command(BaseCommand):
solid=solid, solid=solid,
color=color, color=color,
amount=amount, amount=amount,
notes=notes notes=notes,
).save() ).save()
self.time = time self.time = time
@ -152,26 +149,26 @@ class Command(BaseCommand):
Add a Feeding entry and advance self.time. Add a Feeding entry and advance self.time.
:returns: :returns:
""" """
method = choice(models.Feeding._meta.get_field('method').choices)[0] method = choice(models.Feeding._meta.get_field("method").choices)[0]
amount = None amount = None
if method == 'bottle': if method == "bottle":
amount = Decimal('%d.%d' % (randint(0, 6), randint(0, 9))) amount = Decimal("%d.%d" % (randint(0, 6), randint(0, 9)))
start = self.time + timedelta(minutes=randint(1, 60)) start = self.time + timedelta(minutes=randint(1, 60))
end = start + timedelta(minutes=randint(5, 20)) end = start + timedelta(minutes=randint(5, 20))
notes = '' notes = ""
if choice([True, False, False, False]): if choice([True, False, False, False]):
notes = ' '.join(self.faker.sentences(randint(1, 5))) notes = " ".join(self.faker.sentences(randint(1, 5)))
if end < self.time_now: if end < self.time_now:
models.Feeding.objects.create( models.Feeding.objects.create(
child=self.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,
notes=notes notes=notes,
).save() ).save()
self.time = end self.time = end
@ -197,16 +194,13 @@ class Command(BaseCommand):
minutes = randint(30, 60 * 2) minutes = randint(30, 60 * 2)
end = self.time + timedelta(minutes=minutes) end = self.time + timedelta(minutes=minutes)
notes = '' notes = ""
if choice([True, False, False, False]): if choice([True, False, False, False]):
notes = ' '.join(self.faker.sentences(randint(1, 5))) notes = " ".join(self.faker.sentences(randint(1, 5)))
if end < self.time_now: if end < self.time_now:
models.Sleep.objects.create( models.Sleep.objects.create(
child=self.child, child=self.child, start=self.time, end=end, notes=notes
start=self.time,
end=end,
notes=notes
).save() ).save()
self.time = end self.time = end
@ -218,15 +212,12 @@ class Command(BaseCommand):
""" """
self.temperature = round(uniform(95.0, 102.0), 2) self.temperature = round(uniform(95.0, 102.0), 2)
notes = '' notes = ""
if choice([True, False, False, False]): if choice([True, False, False, False]):
notes = ' '.join(self.faker.sentences(randint(1, 5))) notes = " ".join(self.faker.sentences(randint(1, 5)))
models.Temperature.objects.create( models.Temperature.objects.create(
child=self.child, child=self.child, temperature=self.temperature, time=self.time, notes=notes
temperature=self.temperature,
time=self.time,
notes=notes
).save() ).save()
@transaction.atomic @transaction.atomic
@ -235,7 +226,7 @@ class Command(BaseCommand):
Add a Tummy time entry and advance self.time. Add a Tummy time entry and advance self.time.
:returns: :returns:
""" """
milestone = '' milestone = ""
if choice([True, False]): if choice([True, False]):
milestone = self.faker.sentence() milestone = self.faker.sentence()
start = self.time + timedelta(minutes=randint(1, 60)) start = self.time + timedelta(minutes=randint(1, 60))
@ -245,10 +236,7 @@ class Command(BaseCommand):
if end < self.time_now: if end < self.time_now:
models.TummyTime.objects.create( models.TummyTime.objects.create(
child=self.child, child=self.child, start=start, end=end, milestone=milestone
start=start,
end=end,
milestone=milestone
).save() ).save()
self.time = end self.time = end
@ -260,15 +248,15 @@ class Command(BaseCommand):
""" """
self.weight += uniform(0.1, 0.3) self.weight += uniform(0.1, 0.3)
notes = '' notes = ""
if choice([True, False, False, False]): if choice([True, False, False, False]):
notes = ' '.join(self.faker.sentences(randint(1, 5))) notes = " ".join(self.faker.sentences(randint(1, 5)))
models.Weight.objects.create( models.Weight.objects.create(
child=self.child, child=self.child,
weight=round(self.weight, 2), weight=round(self.weight, 2),
date=self.time.date(), date=self.time.date(),
notes=notes notes=notes,
).save() ).save()
@transaction.atomic @transaction.atomic
@ -279,15 +267,15 @@ class Command(BaseCommand):
""" """
self.height += uniform(0.1, 0.3) self.height += uniform(0.1, 0.3)
notes = '' notes = ""
if choice([True, False, False, False]): if choice([True, False, False, False]):
notes = ' '.join(self.faker.sentences(randint(1, 5))) notes = " ".join(self.faker.sentences(randint(1, 5)))
models.Height.objects.create( models.Height.objects.create(
child=self.child, child=self.child,
height=round(self.height, 2), height=round(self.height, 2),
date=self.time.date(), date=self.time.date(),
notes=notes notes=notes,
).save() ).save()
@transaction.atomic @transaction.atomic
@ -298,15 +286,15 @@ class Command(BaseCommand):
""" """
self.head_circumference += uniform(0.1, 0.3) self.head_circumference += uniform(0.1, 0.3)
notes = '' notes = ""
if choice([True, False, False, False]): if choice([True, False, False, False]):
notes = ' '.join(self.faker.sentences(randint(1, 5))) notes = " ".join(self.faker.sentences(randint(1, 5)))
models.HeadCircumference.objects.create( models.HeadCircumference.objects.create(
child=self.child, child=self.child,
head_circumference=round(self.head_circumference, 2), head_circumference=round(self.head_circumference, 2),
date=self.time.date(), date=self.time.date(),
notes=notes notes=notes,
).save() ).save()
@transaction.atomic @transaction.atomic
@ -317,13 +305,10 @@ class Command(BaseCommand):
""" """
self.bmi += uniform(0.1, 0.3) self.bmi += uniform(0.1, 0.3)
notes = '' notes = ""
if choice([True, False, False, False]): if choice([True, False, False, False]):
notes = ' '.join(self.faker.sentences(randint(1, 5))) notes = " ".join(self.faker.sentences(randint(1, 5)))
models.BMI.objects.create( models.BMI.objects.create(
child=self.child, child=self.child, bmi=round(self.bmi, 2), date=self.time.date(), notes=notes
bmi=round(self.bmi, 2),
date=self.time.date(),
notes=notes
).save() ).save()

View File

@ -4,14 +4,14 @@ from django.core.management.commands import migrate
class Command(migrate.Command): class Command(migrate.Command):
help = 'Creates an initial User (admin/admin) for Baby Buddy.' help = "Creates an initial User (admin/admin) for Baby Buddy."
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
super(Command, self).handle(*args, **kwargs) super(Command, self).handle(*args, **kwargs)
superusers = User.objects.filter(is_superuser=True) superusers = User.objects.filter(is_superuser=True)
if len(superusers) == 0: if len(superusers) == 0:
default_user = User.objects.create_user('admin', password='admin') default_user = User.objects.create_user("admin", password="admin")
default_user.is_superuser = True default_user.is_superuser = True
default_user.is_staff = True default_user.is_staff = True
default_user.save() default_user.save()

View File

@ -12,13 +12,14 @@ from .migrate import Command as Migrate
class Command(BaseCommand): class Command(BaseCommand):
help = 'Reapplies core migrations and generates fake data.' help = "Reapplies core migrations and generates fake data."
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Command, self).__init__(*args, **kwargs) super(Command, self).__init__(*args, **kwargs)
self.UserModel = get_user_model() self.UserModel = get_user_model()
self.username_field = self.UserModel._meta.get_field( self.username_field = self.UserModel._meta.get_field(
self.UserModel.USERNAME_FIELD) self.UserModel.USERNAME_FIELD
)
# Disable system checks for reset. # Disable system checks for reset.
self.requires_system_checks = False self.requires_system_checks = False
@ -28,20 +29,20 @@ class Command(BaseCommand):
Fake().add_arguments(parser) Fake().add_arguments(parser)
def handle(self, *args, **options): def handle(self, *args, **options):
verbosity = options['verbosity'] verbosity = options["verbosity"]
# Flush all existing database records. # Flush all existing database records.
flush = Flush() flush = Flush()
flush.handle(**options) flush.handle(**options)
if verbosity > 0: if verbosity > 0:
self.stdout.write(self.style.SUCCESS('Database flushed.')) self.stdout.write(self.style.SUCCESS("Database flushed."))
# Run migrations for all Baby Buddy apps. # Run migrations for all Baby Buddy apps.
for config in apps.app_configs.values(): for config in apps.app_configs.values():
if path.split(path.split(config.path)[0])[1] == 'babybuddy': if path.split(path.split(config.path)[0])[1] == "babybuddy":
migrate = Migrate() migrate = Migrate()
options['app_label'] = config.name options["app_label"] = config.name
options['migration_name'] = 'zero' options["migration_name"] = "zero"
try: try:
migrate.handle(*args, **options) migrate.handle(*args, **options)
@ -51,18 +52,18 @@ class Command(BaseCommand):
# Run other migrations. # Run other migrations.
migrate = Migrate() migrate = Migrate()
options['app_label'] = None options["app_label"] = None
options['migration_name'] = None options["migration_name"] = None
migrate.handle(*args, **options) migrate.handle(*args, **options)
# Clear cache. # Clear cache.
cache.clear() cache.clear()
if verbosity > 0: if verbosity > 0:
self.stdout.write(self.style.SUCCESS('Cache cleared.')) self.stdout.write(self.style.SUCCESS("Cache cleared."))
# Populate database with fake data. # Populate database with fake data.
fake = Fake() fake = Fake()
fake.handle(*args, **options) fake.handle(*args, **options)
if verbosity > 0: if verbosity > 0:
self.stdout.write(self.style.SUCCESS('Database reset complete.')) self.stdout.write(self.style.SUCCESS("Database reset complete."))

View File

@ -16,63 +16,66 @@ def update_en_us_date_formats():
based on user settings. based on user settings.
""" """
if settings.USE_24_HOUR_TIME_FORMAT: if settings.USE_24_HOUR_TIME_FORMAT:
formats_en_us.DATETIME_FORMAT = 'N j, Y, H:i:s' formats_en_us.DATETIME_FORMAT = "N j, Y, H:i:s"
custom_input_formats = [ custom_input_formats = [
'%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59' "%m/%d/%Y %H:%M:%S", # '10/25/2006 14:30:59'
'%m/%d/%Y %H:%M', # '10/25/2006 14:30' "%m/%d/%Y %H:%M", # '10/25/2006 14:30'
] ]
formats_en_us.SHORT_DATETIME_FORMAT = 'm/d/Y G:i:s' formats_en_us.SHORT_DATETIME_FORMAT = "m/d/Y G:i:s"
formats_en_us.TIME_FORMAT = 'H:i:s' formats_en_us.TIME_FORMAT = "H:i:s"
else: else:
# These formats are added to support the locale style of Baby Buddy's # These formats are added to support the locale style of Baby Buddy's
# frontend library, which uses momentjs. # frontend library, which uses momentjs.
custom_input_formats = [ custom_input_formats = [
'%m/%d/%Y %I:%M:%S %p', # '10/25/2006 2:30:59 PM' "%m/%d/%Y %I:%M:%S %p", # '10/25/2006 2:30:59 PM'
'%m/%d/%Y %I:%M %p', # '10/25/2006 2:30 PM' "%m/%d/%Y %I:%M %p", # '10/25/2006 2:30 PM'
] ]
# Add custom "short" version of `MONTH_DAY_FORMAT`. # Add custom "short" version of `MONTH_DAY_FORMAT`.
formats_en_us.SHORT_MONTH_DAY_FORMAT = 'M j' formats_en_us.SHORT_MONTH_DAY_FORMAT = "M j"
# Append all other input formats from the base locale. # Append all other input formats from the base locale.
formats_en_us.DATETIME_INPUT_FORMATS = \ formats_en_us.DATETIME_INPUT_FORMATS = (
custom_input_formats + formats_en_us.DATETIME_INPUT_FORMATS custom_input_formats + formats_en_us.DATETIME_INPUT_FORMATS
)
def update_en_gb_date_formats(): def update_en_gb_date_formats():
if settings.USE_24_HOUR_TIME_FORMAT: if settings.USE_24_HOUR_TIME_FORMAT:
# 25 October 2006 14:30:00 # 25 October 2006 14:30:00
formats_en_gb.DATETIME_FORMAT = 'j F Y H:i:s' formats_en_gb.DATETIME_FORMAT = "j F Y H:i:s"
custom_input_formats = [ custom_input_formats = [
'%d/%m/%Y %H:%M:%S', # '25/10/2006 14:30:59' "%d/%m/%Y %H:%M:%S", # '25/10/2006 14:30:59'
'%d/%m/%Y %H:%M', # '25/10/2006 14:30' "%d/%m/%Y %H:%M", # '25/10/2006 14:30'
] ]
formats_en_gb.SHORT_DATETIME_FORMAT = 'd/m/Y H:i' formats_en_gb.SHORT_DATETIME_FORMAT = "d/m/Y H:i"
formats_en_gb.TIME_FORMAT = 'H:i' formats_en_gb.TIME_FORMAT = "H:i"
else: else:
formats_en_gb.DATETIME_FORMAT = 'j F Y f a' # 25 October 2006 2:30 p.m formats_en_gb.DATETIME_FORMAT = "j F Y f a" # 25 October 2006 2:30 p.m
# These formats are added to support the locale style of Baby Buddy's # These formats are added to support the locale style of Baby Buddy's
# frontend library, which uses momentjs. # frontend library, which uses momentjs.
custom_input_formats = [ custom_input_formats = [
'%d/%m/%Y %I:%M:%S %p', # '25/10/2006 2:30:59 PM' "%d/%m/%Y %I:%M:%S %p", # '25/10/2006 2:30:59 PM'
'%d/%m/%Y %I:%M %p', # '25/10/2006 2:30 PM' "%d/%m/%Y %I:%M %p", # '25/10/2006 2:30 PM'
] ]
# Append all other input formats from the base locale. # Append all other input formats from the base locale.
formats_en_gb.DATETIME_INPUT_FORMATS = \ formats_en_gb.DATETIME_INPUT_FORMATS = (
custom_input_formats + formats_en_gb.DATETIME_INPUT_FORMATS custom_input_formats + formats_en_gb.DATETIME_INPUT_FORMATS
)
class UserLanguageMiddleware: class UserLanguageMiddleware:
""" """
Customizes settings based on user language setting. Customizes settings based on user language setting.
""" """
def __init__(self, get_response): def __init__(self, get_response):
self.get_response = get_response self.get_response = get_response
def __call__(self, request): def __call__(self, request):
user = request.user user = request.user
if hasattr(user, 'settings') and user.settings.language: if hasattr(user, "settings") and user.settings.language:
language = user.settings.language language = user.settings.language
elif request.LANGUAGE_CODE: elif request.LANGUAGE_CODE:
language = request.LANGUAGE_CODE language = request.LANGUAGE_CODE
@ -80,9 +83,9 @@ class UserLanguageMiddleware:
language = settings.LANGUAGE_CODE language = settings.LANGUAGE_CODE
if language: if language:
if language == 'en-US': if language == "en-US":
update_en_us_date_formats() update_en_us_date_formats()
elif language == 'en-GB': elif language == "en-GB":
update_en_gb_date_formats() update_en_gb_date_formats()
# Set the language before generating the response. # Set the language before generating the response.
@ -104,12 +107,13 @@ class UserTimezoneMiddleware:
`django.contrib.auth.middleware.AuthenticationMiddleware` because it uses `django.contrib.auth.middleware.AuthenticationMiddleware` because it uses
the request.user object. the request.user object.
""" """
def __init__(self, get_response): def __init__(self, get_response):
self.get_response = get_response self.get_response = get_response
def __call__(self, request): def __call__(self, request):
user = request.user user = request.user
if hasattr(user, 'settings') and user.settings.timezone: if hasattr(user, "settings") and user.settings.timezone:
try: try:
timezone.activate(pytz.timezone(user.settings.timezone)) timezone.activate(pytz.timezone(user.settings.timezone))
except pytz.UnknownTimeZoneError: except pytz.UnknownTimeZoneError:
@ -121,20 +125,21 @@ class RollingSessionMiddleware:
""" """
Periodically resets the session expiry for existing sessions. Periodically resets the session expiry for existing sessions.
""" """
def __init__(self, get_response): def __init__(self, get_response):
self.get_response = get_response self.get_response = get_response
def __call__(self, request): def __call__(self, request):
if request.session.keys(): if request.session.keys():
session_refresh = request.session.get('session_refresh') session_refresh = request.session.get("session_refresh")
if session_refresh: if session_refresh:
try: try:
delta = int(time.time()) - session_refresh delta = int(time.time()) - session_refresh
except (ValueError, TypeError): except (ValueError, TypeError):
delta = settings.ROLLING_SESSION_REFRESH + 1 delta = settings.ROLLING_SESSION_REFRESH + 1
if delta > settings.ROLLING_SESSION_REFRESH: if delta > settings.ROLLING_SESSION_REFRESH:
request.session['session_refresh'] = int(time.time()) request.session["session_refresh"] = int(time.time())
request.session.set_expiry(settings.SESSION_COOKIE_AGE) request.session.set_expiry(settings.SESSION_COOKIE_AGE)
else: else:
request.session['session_refresh'] = int(time.time()) request.session["session_refresh"] = int(time.time())
return self.get_response(request) return self.get_response(request)

View File

@ -16,11 +16,44 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Settings', name="Settings",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('dashboard_refresh_rate', models.DurationField(blank=True, choices=[(None, 'disabled'), (datetime.timedelta(0, 60), '1 min.'), (datetime.timedelta(0, 120), '2 min.'), (datetime.timedelta(0, 180), '3 min.'), (datetime.timedelta(0, 240), '4 min.'), (datetime.timedelta(0, 300), '5 min.'), (datetime.timedelta(0, 600), '10 min.'), (datetime.timedelta(0, 900), '15 min.'), (datetime.timedelta(0, 1800), '30 min.')], default=datetime.timedelta(0, 60), null=True, verbose_name='Refresh rate')), "id",
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"dashboard_refresh_rate",
models.DurationField(
blank=True,
choices=[
(None, "disabled"),
(datetime.timedelta(0, 60), "1 min."),
(datetime.timedelta(0, 120), "2 min."),
(datetime.timedelta(0, 180), "3 min."),
(datetime.timedelta(0, 240), "4 min."),
(datetime.timedelta(0, 300), "5 min."),
(datetime.timedelta(0, 600), "10 min."),
(datetime.timedelta(0, 900), "15 min."),
(datetime.timedelta(0, 1800), "30 min."),
],
default=datetime.timedelta(0, 60),
null=True,
verbose_name="Refresh rate",
),
),
(
"user",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
], ],
), ),
] ]

View File

@ -3,8 +3,8 @@ from django.db import migrations
def add_settings(apps, schema_editor): def add_settings(apps, schema_editor):
Settings = apps.get_model('babybuddy', 'Settings') Settings = apps.get_model("babybuddy", "Settings")
User = apps.get_model('auth', 'User') User = apps.get_model("auth", "User")
for user in User.objects.all(): for user in User.objects.all():
if Settings.objects.filter(user=user).count() == 0: if Settings.objects.filter(user=user).count() == 0:
settings = Settings.objects.create(user=user) settings = Settings.objects.create(user=user)
@ -16,7 +16,7 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('babybuddy', '0001_initial'), ("babybuddy", "0001_initial"),
] ]
operations = [ operations = [

View File

@ -7,13 +7,30 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('babybuddy', '0002_add_settings'), ("babybuddy", "0002_add_settings"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='settings', model_name="settings",
name='dashboard_refresh_rate', name="dashboard_refresh_rate",
field=models.DurationField(blank=True, choices=[(None, 'disabled'), (datetime.timedelta(0, 60), '1 min.'), (datetime.timedelta(0, 120), '2 min.'), (datetime.timedelta(0, 180), '3 min.'), (datetime.timedelta(0, 240), '4 min.'), (datetime.timedelta(0, 300), '5 min.'), (datetime.timedelta(0, 600), '10 min.'), (datetime.timedelta(0, 900), '15 min.'), (datetime.timedelta(0, 1800), '30 min.')], default=datetime.timedelta(0, 60), help_text='This setting will only be used when a browser does not support refresh on focus.', null=True, verbose_name='Refresh rate'), field=models.DurationField(
blank=True,
choices=[
(None, "disabled"),
(datetime.timedelta(0, 60), "1 min."),
(datetime.timedelta(0, 120), "2 min."),
(datetime.timedelta(0, 180), "3 min."),
(datetime.timedelta(0, 240), "4 min."),
(datetime.timedelta(0, 300), "5 min."),
(datetime.timedelta(0, 600), "10 min."),
(datetime.timedelta(0, 900), "15 min."),
(datetime.timedelta(0, 1800), "30 min."),
],
default=datetime.timedelta(0, 60),
help_text="This setting will only be used when a browser does not support refresh on focus.",
null=True,
verbose_name="Refresh rate",
),
), ),
] ]

View File

@ -6,13 +6,15 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('babybuddy', '0003_add_refresh_help_text'), ("babybuddy", "0003_add_refresh_help_text"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='settings', model_name="settings",
name='language', name="language",
field=models.CharField(choices=[], default='en', max_length=255, verbose_name='Language'), field=models.CharField(
choices=[], default="en", max_length=255, verbose_name="Language"
),
), ),
] ]

View File

@ -6,13 +6,18 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('babybuddy', '0004_settings_language'), ("babybuddy", "0004_settings_language"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='settings', model_name="settings",
name='language', name="language",
field=models.CharField(choices=[('en', 'English'), ('fr', 'French')], default='en', max_length=255, verbose_name='Language'), field=models.CharField(
choices=[("en", "English"), ("fr", "French")],
default="en",
max_length=255,
verbose_name="Language",
),
), ),
] ]

View File

@ -6,13 +6,18 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('babybuddy', '0005_auto_20190502_1701'), ("babybuddy", "0005_auto_20190502_1701"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='settings', model_name="settings",
name='language', name="language",
field=models.CharField(choices=[('en', 'English'), ('fr', 'French'), ('sv', 'Swedish')], default='en', max_length=255, verbose_name='Language'), field=models.CharField(
choices=[("en", "English"), ("fr", "French"), ("sv", "Swedish")],
default="en",
max_length=255,
verbose_name="Language",
),
), ),
] ]

View File

@ -6,13 +6,23 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('babybuddy', '0006_auto_20190502_1744'), ("babybuddy", "0006_auto_20190502_1744"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='settings', model_name="settings",
name='language', name="language",
field=models.CharField(choices=[('en', 'English'), ('fr', 'French'), ('de', 'German'), ('sv', 'Swedish')], default='en', max_length=255, verbose_name='Language'), field=models.CharField(
choices=[
("en", "English"),
("fr", "French"),
("de", "German"),
("sv", "Swedish"),
],
default="en",
max_length=255,
verbose_name="Language",
),
), ),
] ]

View File

@ -6,13 +6,25 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('babybuddy', '0007_auto_20190607_1422'), ("babybuddy", "0007_auto_20190607_1422"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='settings', model_name="settings",
name='language', name="language",
field=models.CharField(choices=[('en', 'English'), ('fr', 'French'), ('de', 'German'), ('es', 'Spanish'), ('sv', 'Swedish'), ('tr', 'Turkish')], default='en', max_length=255, verbose_name='Language'), field=models.CharField(
choices=[
("en", "English"),
("fr", "French"),
("de", "German"),
("es", "Spanish"),
("sv", "Swedish"),
("tr", "Turkish"),
],
default="en",
max_length=255,
verbose_name="Language",
),
), ),
] ]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -6,13 +6,15 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('babybuddy', '0013_auto_20210411_1241'), ("babybuddy", "0013_auto_20210411_1241"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='settings', model_name="settings",
name='dashboard_hide_empty', name="dashboard_hide_empty",
field=models.BooleanField(default=False, verbose_name='Hide Empty Dashboard Cards'), field=models.BooleanField(
default=False, verbose_name="Hide Empty Dashboard Cards"
),
), ),
] ]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,24 +5,25 @@ from django.utils import timezone
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('babybuddy', '0016_alter_settings_timezone'), ("babybuddy", "0016_alter_settings_timezone"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='settings', model_name="settings",
name='dashboard_hide_age', name="dashboard_hide_age",
field=models.DurationField( field=models.DurationField(
choices=[ choices=[
(None, 'show all data'), (None, "show all data"),
(timezone.timedelta(days=1), '1 day'), (timezone.timedelta(days=1), "1 day"),
(timezone.timedelta(days=2), '2 days'), (timezone.timedelta(days=2), "2 days"),
(timezone.timedelta(days=3), '3 days'), (timezone.timedelta(days=3), "3 days"),
(timezone.timedelta(weeks=1), '1 week'), (timezone.timedelta(weeks=1), "1 week"),
(timezone.timedelta(weeks=4), '4 weeks') (timezone.timedelta(weeks=4), "4 weeks"),
], ],
default=None, default=None,
null=True, null=True,
verbose_name='Hide data older than'), verbose_name="Hide data older than",
),
), ),
] ]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4,27 +4,29 @@ from django.db import migrations
def update_language_en_to_en_us(apps, schema_editor): def update_language_en_to_en_us(apps, schema_editor):
Settings = apps.get_model('babybuddy', 'Settings') Settings = apps.get_model("babybuddy", "Settings")
for settings in Settings.objects.all(): for settings in Settings.objects.all():
if settings.language == 'en': if settings.language == "en":
settings.language = 'en-US' settings.language = "en-US"
settings.save() settings.save()
def update_language_en_us_to_en(apps, schema_editor): def update_language_en_us_to_en(apps, schema_editor):
Settings = apps.get_model('babybuddy', 'Settings') Settings = apps.get_model("babybuddy", "Settings")
for settings in Settings.objects.all(): for settings in Settings.objects.all():
if settings.language == 'en-US': if settings.language == "en-US":
settings.language = 'en' settings.language = "en"
settings.save() settings.save()
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('babybuddy', '0019_alter_settings_timezone'), ("babybuddy", "0019_alter_settings_timezone"),
] ]
operations = [ operations = [
migrations.RunPython(update_language_en_to_en_us, reverse_code=update_language_en_us_to_en), migrations.RunPython(
update_language_en_to_en_us, reverse_code=update_language_en_us_to_en
),
] ]

View File

@ -1,26 +1,29 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.contrib.auth.mixins import AccessMixin, \ from django.contrib.auth.mixins import (
LoginRequiredMixin as LoginRequiredMixInBase, \ AccessMixin,
PermissionRequiredMixin as PermissionRequiredMixinBase LoginRequiredMixin as LoginRequiredMixInBase,
PermissionRequiredMixin as PermissionRequiredMixinBase,
)
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.decorators.cache import never_cache from django.views.decorators.cache import never_cache
@method_decorator(never_cache, name='dispatch') @method_decorator(never_cache, name="dispatch")
class LoginRequiredMixin(LoginRequiredMixInBase): class LoginRequiredMixin(LoginRequiredMixInBase):
pass pass
@method_decorator(never_cache, name='dispatch') @method_decorator(never_cache, name="dispatch")
class PermissionRequiredMixin(PermissionRequiredMixinBase): class PermissionRequiredMixin(PermissionRequiredMixinBase):
login_url = '/login' login_url = "/login"
@method_decorator(never_cache, name='dispatch') @method_decorator(never_cache, name="dispatch")
class StaffOnlyMixin(AccessMixin): class StaffOnlyMixin(AccessMixin):
""" """
Verify the current user is staff. Verify the current user is staff.
""" """
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not request.user.is_staff: if not request.user.is_staff:
return self.handle_no_permission() return self.handle_no_permission()

View File

@ -16,58 +16,61 @@ from rest_framework.authtoken.models import Token
class Settings(models.Model): class Settings(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE) user = models.OneToOneField(User, on_delete=models.CASCADE)
dashboard_refresh_rate = models.DurationField( dashboard_refresh_rate = models.DurationField(
verbose_name=_('Refresh rate'), verbose_name=_("Refresh rate"),
help_text=_('If supported by browser, the dashboard will only refresh ' help_text=_(
'when visible, and also when receiving focus.'), "If supported by browser, the dashboard will only refresh "
"when visible, and also when receiving focus."
),
blank=True, blank=True,
null=True, null=True,
default=timezone.timedelta(minutes=1), default=timezone.timedelta(minutes=1),
choices=[ choices=[
(None, _('disabled')), (None, _("disabled")),
(timezone.timedelta(minutes=1), _('1 min.')), (timezone.timedelta(minutes=1), _("1 min.")),
(timezone.timedelta(minutes=2), _('2 min.')), (timezone.timedelta(minutes=2), _("2 min.")),
(timezone.timedelta(minutes=3), _('3 min.')), (timezone.timedelta(minutes=3), _("3 min.")),
(timezone.timedelta(minutes=4), _('4 min.')), (timezone.timedelta(minutes=4), _("4 min.")),
(timezone.timedelta(minutes=5), _('5 min.')), (timezone.timedelta(minutes=5), _("5 min.")),
(timezone.timedelta(minutes=10), _('10 min.')), (timezone.timedelta(minutes=10), _("10 min.")),
(timezone.timedelta(minutes=15), _('15 min.')), (timezone.timedelta(minutes=15), _("15 min.")),
(timezone.timedelta(minutes=30), _('30 min.')), (timezone.timedelta(minutes=30), _("30 min.")),
]) ],
)
dashboard_hide_empty = models.BooleanField( dashboard_hide_empty = models.BooleanField(
verbose_name=_('Hide Empty Dashboard Cards'), verbose_name=_("Hide Empty Dashboard Cards"), default=False, editable=True
default=False,
editable=True
) )
dashboard_hide_age = models.DurationField( dashboard_hide_age = models.DurationField(
verbose_name=_('Hide data older than'), verbose_name=_("Hide data older than"),
help_text=_('This setting controls which data will be shown ' help_text=_(
'in the dashboard.'), "This setting controls which data will be shown " "in the dashboard."
),
blank=True, blank=True,
null=True, null=True,
default=None, default=None,
choices=[ choices=[
(None, _('show all data')), (None, _("show all data")),
(timezone.timedelta(days=1), _('1 day')), (timezone.timedelta(days=1), _("1 day")),
(timezone.timedelta(days=2), _('2 days')), (timezone.timedelta(days=2), _("2 days")),
(timezone.timedelta(days=3), _('3 days')), (timezone.timedelta(days=3), _("3 days")),
(timezone.timedelta(weeks=1), _('1 week')), (timezone.timedelta(weeks=1), _("1 week")),
(timezone.timedelta(weeks=4), _('4 weeks')), (timezone.timedelta(weeks=4), _("4 weeks")),
]) ],
)
language = models.CharField( language = models.CharField(
choices=settings.LANGUAGES, choices=settings.LANGUAGES,
default=settings.LANGUAGE_CODE, default=settings.LANGUAGE_CODE,
max_length=255, max_length=255,
verbose_name=_('Language') verbose_name=_("Language"),
) )
timezone = models.CharField( timezone = models.CharField(
choices=tuple(zip(pytz.common_timezones, pytz.common_timezones)), choices=tuple(zip(pytz.common_timezones, pytz.common_timezones)),
default=timezone.get_default_timezone_name(), default=timezone.get_default_timezone_name(),
max_length=100, max_length=100,
verbose_name=_('Timezone') verbose_name=_("Timezone"),
) )
def __str__(self): def __str__(self):
return str(format_lazy(_('{user}\'s Settings'), user=self.user)) return str(format_lazy(_("{user}'s Settings"), user=self.user))
def api_key(self, reset=False): def api_key(self, reset=False):
""" """

View File

@ -5,13 +5,7 @@ from django.utils.translation import gettext_lazy as _
from dotenv import load_dotenv, find_dotenv from dotenv import load_dotenv, find_dotenv
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname( BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
os.path.dirname(
os.path.dirname(
os.path.abspath(__file__)
)
)
)
# Environment variables # Environment variables
# Check for and load environment variables from a .env file. # Check for and load environment variables from a .env file.
@ -20,63 +14,61 @@ load_dotenv(find_dotenv())
# Required settings # Required settings
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '*').split(',') ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "*").split(",")
SECRET_KEY = os.environ.get('SECRET_KEY') or None SECRET_KEY = os.environ.get("SECRET_KEY") or None
DEBUG = bool(strtobool(os.environ.get('DEBUG') or 'False')) DEBUG = bool(strtobool(os.environ.get("DEBUG") or "False"))
# Applications # Applications
# https://docs.djangoproject.com/en/3.0/ref/applications/ # https://docs.djangoproject.com/en/3.0/ref/applications/
INSTALLED_APPS = [ INSTALLED_APPS = [
'api', "api",
'babybuddy', "babybuddy",
'core', "core",
'dashboard', "dashboard",
'reports', "reports",
"axes",
'axes', "django_filters",
'django_filters', "rest_framework",
'rest_framework', "rest_framework.authtoken",
'rest_framework.authtoken', "widget_tweaks",
'widget_tweaks', "imagekit",
'imagekit', "storages",
'storages', "import_export",
'import_export', "django.contrib.admin",
"django.contrib.auth",
'django.contrib.admin', "django.contrib.contenttypes",
'django.contrib.auth', "django.contrib.sessions",
'django.contrib.contenttypes', "django.contrib.messages",
'django.contrib.sessions', "django.contrib.staticfiles",
'django.contrib.messages', "django.contrib.humanize",
'django.contrib.staticfiles',
'django.contrib.humanize',
] ]
# Middleware # Middleware
# https://docs.djangoproject.com/en/3.0/ref/middleware/ # https://docs.djangoproject.com/en/3.0/ref/middleware/
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', "django.middleware.security.SecurityMiddleware",
'whitenoise.middleware.WhiteNoiseMiddleware', "whitenoise.middleware.WhiteNoiseMiddleware",
'django.contrib.sessions.middleware.SessionMiddleware', "django.contrib.sessions.middleware.SessionMiddleware",
'babybuddy.middleware.RollingSessionMiddleware', "babybuddy.middleware.RollingSessionMiddleware",
'django.contrib.auth.middleware.AuthenticationMiddleware', "django.contrib.auth.middleware.AuthenticationMiddleware",
'babybuddy.middleware.UserTimezoneMiddleware', "babybuddy.middleware.UserTimezoneMiddleware",
'django.middleware.locale.LocaleMiddleware', "django.middleware.locale.LocaleMiddleware",
'babybuddy.middleware.UserLanguageMiddleware', "babybuddy.middleware.UserLanguageMiddleware",
'django.middleware.common.CommonMiddleware', "django.middleware.common.CommonMiddleware",
'django.middleware.csrf.CsrfViewMiddleware', "django.middleware.csrf.CsrfViewMiddleware",
'django.contrib.messages.middleware.MessageMiddleware', "django.contrib.messages.middleware.MessageMiddleware",
'django.middleware.clickjacking.XFrameOptionsMiddleware', "django.middleware.clickjacking.XFrameOptionsMiddleware",
'axes.middleware.AxesMiddleware', "axes.middleware.AxesMiddleware",
] ]
# URL dispatcher # URL dispatcher
# https://docs.djangoproject.com/en/3.0/topics/http/urls/ # https://docs.djangoproject.com/en/3.0/topics/http/urls/
ROOT_URLCONF = 'babybuddy.urls' ROOT_URLCONF = "babybuddy.urls"
# Templates # Templates
@ -84,15 +76,15 @@ ROOT_URLCONF = 'babybuddy.urls'
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'DIRS': [], "DIRS": [],
'APP_DIRS': True, "APP_DIRS": True,
'OPTIONS': { "OPTIONS": {
'context_processors': [ "context_processors": [
'django.template.context_processors.debug', "django.template.context_processors.debug",
'django.template.context_processors.request', "django.template.context_processors.request",
'django.contrib.auth.context_processors.auth', "django.contrib.auth.context_processors.auth",
'django.contrib.messages.context_processors.messages', "django.contrib.messages.context_processors.messages",
], ],
}, },
}, },
@ -103,28 +95,30 @@ TEMPLATES = [
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases # https://docs.djangoproject.com/en/3.0/ref/settings/#databases
config = { config = {
'ENGINE': os.getenv('DB_ENGINE') or 'django.db.backends.sqlite3', "ENGINE": os.getenv("DB_ENGINE") or "django.db.backends.sqlite3",
'NAME': os.getenv('DB_NAME') or os.path.join(BASE_DIR, 'data/db.sqlite3') "NAME": os.getenv("DB_NAME") or os.path.join(BASE_DIR, "data/db.sqlite3"),
} }
if os.getenv('DB_USER'): if os.getenv("DB_USER"):
config['USER'] = os.getenv('DB_USER') config["USER"] = os.getenv("DB_USER")
if os.environ.get('DB_PASSWORD') or os.environ.get('POSTGRES_PASSWORD'): if os.environ.get("DB_PASSWORD") or os.environ.get("POSTGRES_PASSWORD"):
config['PASSWORD'] = os.environ.get('DB_PASSWORD') or os.environ.get('POSTGRES_PASSWORD') config["PASSWORD"] = os.environ.get("DB_PASSWORD") or os.environ.get(
if os.getenv('DB_HOST'): "POSTGRES_PASSWORD"
config['HOST'] = os.getenv('DB_HOST') )
if os.getenv('DB_PORT'): if os.getenv("DB_HOST"):
config['PORT'] = os.getenv('DB_PORT') config["HOST"] = os.getenv("DB_HOST")
if os.getenv("DB_PORT"):
config["PORT"] = os.getenv("DB_PORT")
DATABASES = {'default': config} DATABASES = {"default": config}
# Cache # Cache
# https://docs.djangoproject.com/en/3.0/topics/cache/ # https://docs.djangoproject.com/en/3.0/topics/cache/
CACHES = { CACHES = {
'default': { "default": {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache', "BACKEND": "django.core.cache.backends.db.DatabaseCache",
'LOCATION': 'cache_default', "LOCATION": "cache_default",
} }
} }
@ -132,22 +126,22 @@ CACHES = {
# WGSI # WGSI
# https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ # https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/
WSGI_APPLICATION = 'babybuddy.wsgi.application' WSGI_APPLICATION = "babybuddy.wsgi.application"
# Authentication # Authentication
# https://docs.djangoproject.com/en/3.0/topics/auth/default/ # https://docs.djangoproject.com/en/3.0/topics/auth/default/
AUTHENTICATION_BACKENDS = [ AUTHENTICATION_BACKENDS = [
'axes.backends.AxesBackend', "axes.backends.AxesBackend",
'django.contrib.auth.backends.ModelBackend', "django.contrib.auth.backends.ModelBackend",
] ]
LOGIN_REDIRECT_URL = 'babybuddy:root-router' LOGIN_REDIRECT_URL = "babybuddy:root-router"
LOGIN_URL = 'babybuddy:login' LOGIN_URL = "babybuddy:login"
LOGOUT_REDIRECT_URL = 'babybuddy:login' LOGOUT_REDIRECT_URL = "babybuddy:login"
# Timezone # Timezone
@ -155,32 +149,32 @@ LOGOUT_REDIRECT_URL = 'babybuddy:login'
USE_TZ = True USE_TZ = True
TIME_ZONE = os.environ.get('TIME_ZONE') or 'UTC' TIME_ZONE = os.environ.get("TIME_ZONE") or "UTC"
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/ # https://docs.djangoproject.com/en/3.0/topics/i18n/
USE_I18N = True USE_I18N = True
LANGUAGE_CODE = 'en-US' LANGUAGE_CODE = "en-US"
LOCALE_PATHS = [ LOCALE_PATHS = [
os.path.join(BASE_DIR, "locale"), os.path.join(BASE_DIR, "locale"),
] ]
LANGUAGES = [ LANGUAGES = [
('en-US', _('English (US)')), ("en-US", _("English (US)")),
('en-GB', _('English (UK)')), ("en-GB", _("English (UK)")),
('nl', _('Dutch')), ("nl", _("Dutch")),
('fr', _('French')), ("fr", _("French")),
('fi', _('Finnish')), ("fi", _("Finnish")),
('de', _('German')), ("de", _("German")),
('it', _('Italian')), ("it", _("Italian")),
('pl', _('Polish')), ("pl", _("Polish")),
('pt', _('Portuguese')), ("pt", _("Portuguese")),
('es', _('Spanish')), ("es", _("Spanish")),
('sv', _('Swedish')), ("sv", _("Swedish")),
('tr', _('Turkish')) ("tr", _("Turkish")),
] ]
@ -195,49 +189,51 @@ USE_L10N = True
# conditionals on this setting. See babybuddy/forms/en/formats.py for an example # conditionals on this setting. See babybuddy/forms/en/formats.py for an example
# implementation for the English locale. # implementation for the English locale.
USE_24_HOUR_TIME_FORMAT = bool(strtobool(os.environ.get('USE_24_HOUR_TIME_FORMAT') or 'False')) USE_24_HOUR_TIME_FORMAT = bool(
strtobool(os.environ.get("USE_24_HOUR_TIME_FORMAT") or "False")
)
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/ # https://docs.djangoproject.com/en/3.0/howto/static-files/
# http://whitenoise.evans.io/en/stable/django.html # http://whitenoise.evans.io/en/stable/django.html
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
STATICFILES_FINDERS = [ STATICFILES_FINDERS = [
'django.contrib.staticfiles.finders.FileSystemFinder', "django.contrib.staticfiles.finders.FileSystemFinder",
'django.contrib.staticfiles.finders.AppDirectoriesFinder', "django.contrib.staticfiles.finders.AppDirectoriesFinder",
] ]
STATIC_URL = 'static/' STATIC_URL = "static/"
STATIC_ROOT = os.path.join(BASE_DIR, 'static') STATIC_ROOT = os.path.join(BASE_DIR, "static")
WHITENOISE_ROOT = os.path.join(BASE_DIR, 'static', 'babybuddy', 'root') WHITENOISE_ROOT = os.path.join(BASE_DIR, "static", "babybuddy", "root")
# Media files (User uploaded content) # Media files (User uploaded content)
# https://docs.djangoproject.com/en/3.0/topics/files/ # https://docs.djangoproject.com/en/3.0/topics/files/
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = 'media/' MEDIA_URL = "media/"
AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME') or None AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_STORAGE_BUCKET_NAME") or None
AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID') or None AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID") or None
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY') or None AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY") or None
if AWS_STORAGE_BUCKET_NAME: if AWS_STORAGE_BUCKET_NAME:
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
# Security # Security
# https://docs.djangoproject.com/en/3.2/ref/settings/#secure-proxy-ssl-header # https://docs.djangoproject.com/en/3.2/ref/settings/#secure-proxy-ssl-header
if os.environ.get('SECURE_PROXY_SSL_HEADER'): if os.environ.get("SECURE_PROXY_SSL_HEADER"):
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# https://docs.djangoproject.com/en/3.2/topics/http/sessions/#settings # https://docs.djangoproject.com/en/3.2/topics/http/sessions/#settings
SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_HTTPONLY = True
@ -250,19 +246,19 @@ CSRF_COOKIE_HTTPONLY = True
# https://docs.djangoproject.com/en/3.2/topics/auth/passwords/ # https://docs.djangoproject.com/en/3.2/topics/auth/passwords/
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
'OPTIONS': { "OPTIONS": {
'min_length': 8, "min_length": 8,
} },
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
}, },
{ {
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
}, },
] ]
@ -270,31 +266,28 @@ AUTH_PASSWORD_VALIDATORS = [
# https://www.django-rest-framework.org/ # https://www.django-rest-framework.org/
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [ "DEFAULT_AUTHENTICATION_CLASSES": [
'rest_framework.authentication.SessionAuthentication', "rest_framework.authentication.SessionAuthentication",
'rest_framework.authentication.TokenAuthentication', "rest_framework.authentication.TokenAuthentication",
], ],
'DEFAULT_FILTER_BACKENDS': [ "DEFAULT_FILTER_BACKENDS": [
'django_filters.rest_framework.DjangoFilterBackend', "django_filters.rest_framework.DjangoFilterBackend",
], ],
'DEFAULT_METADATA_CLASS': 'api.metadata.APIMetadata', "DEFAULT_METADATA_CLASS": "api.metadata.APIMetadata",
'DEFAULT_PAGINATION_CLASS': "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
'rest_framework.pagination.LimitOffsetPagination', "DEFAULT_PERMISSION_CLASSES": ["api.permissions.BabyBuddyDjangoModelPermissions"],
'DEFAULT_PERMISSION_CLASSES': [ "DEFAULT_RENDERER_CLASSES": [
'api.permissions.BabyBuddyDjangoModelPermissions' "rest_framework.renderers.JSONRenderer",
], ],
'DEFAULT_RENDERER_CLASSES': [ "PAGE_SIZE": 100,
'rest_framework.renderers.JSONRenderer',
],
'PAGE_SIZE': 100
} }
# Import/Export configuration # Import/Export configuration
# See https://django-import-export.readthedocs.io/ # See https://django-import-export.readthedocs.io/
IMPORT_EXPORT_IMPORT_PERMISSION_CODE = 'add' IMPORT_EXPORT_IMPORT_PERMISSION_CODE = "add"
IMPORT_EXPORT_EXPORT_PERMISSION_CODE = 'change' IMPORT_EXPORT_EXPORT_PERMISSION_CODE = "change"
IMPORT_EXPORT_USE_TRANSACTIONS = True IMPORT_EXPORT_USE_TRANSACTIONS = True
@ -314,13 +307,13 @@ ROLLING_SESSION_REFRESH = 86400
# Set default auto field for models. # Set default auto field for models.
# See https://docs.djangoproject.com/en/3.2/releases/3.2/#customizing-type-of-auto-created-primary-keys # See https://docs.djangoproject.com/en/3.2/releases/3.2/#customizing-type-of-auto-created-primary-keys
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
# Baby Buddy configuration # Baby Buddy configuration
# See README.md#configuration for details about these settings. # See README.md#configuration for details about these settings.
BABY_BUDDY = { BABY_BUDDY = {
'NAP_START_MIN': os.environ.get('NAP_START_MIN') or '06:00', "NAP_START_MIN": os.environ.get("NAP_START_MIN") or "06:00",
'NAP_START_MAX': os.environ.get('NAP_START_MAX') or '18:00', "NAP_START_MAX": os.environ.get("NAP_START_MAX") or "18:00",
'ALLOW_UPLOADS': bool(strtobool(os.environ.get('ALLOW_UPLOADS') or 'True')) "ALLOW_UPLOADS": bool(strtobool(os.environ.get("ALLOW_UPLOADS") or "True")),
} }

View File

@ -1,9 +1,9 @@
from .base import * from .base import *
SECRET_KEY = 'CISECRETKEYIGUESS' SECRET_KEY = "CISECRETKEYIGUESS"
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/ # https://docs.djangoproject.com/en/3.0/howto/static-files/
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage' STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage"

View File

@ -3,7 +3,7 @@ from .base import *
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ # https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
SECRET_KEY = 'CHANGE ME' SECRET_KEY = "CHANGE ME"
DEBUG = True DEBUG = True
@ -14,13 +14,13 @@ DEBUG = True
# production static files. # production static files.
# DEBUG = False # DEBUG = False
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage' STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage"
# Django Rest Framework # Django Rest Framework
# https://www.django-rest-framework.org/ # https://www.django-rest-framework.org/
REST_FRAMEWORK['DEFAULT_RENDERER_CLASSES'] = ( REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"] = (
'rest_framework.renderers.JSONRenderer', "rest_framework.renderers.JSONRenderer",
'rest_framework.renderers.BrowsableAPIRenderer', "rest_framework.renderers.BrowsableAPIRenderer",
) )

View File

@ -5,32 +5,32 @@ from .base import *
# Default to not allow uploads. # Default to not allow uploads.
# Heroku does not support file storage for this functionality. # Heroku does not support file storage for this functionality.
BABY_BUDDY['ALLOW_UPLOADS'] = bool(strtobool(os.environ.get('ALLOW_UPLOADS') or 'False')) BABY_BUDDY["ALLOW_UPLOADS"] = bool(
strtobool(os.environ.get("ALLOW_UPLOADS") or "False")
)
# Database # Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases # https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = { DATABASES = {"default": dj_database_url.config(conn_max_age=500)}
'default': dj_database_url.config(conn_max_age=500)
}
# Email # Email
# https://docs.djangoproject.com/en/3.0/topics/email/ # https://docs.djangoproject.com/en/3.0/topics/email/
# https://devcenter.heroku.com/articles/sendgrid#python # https://devcenter.heroku.com/articles/sendgrid#python
SENDGRID_USERNAME = os.environ.get('SENDGRID_USERNAME', None) # noqa: F405 SENDGRID_USERNAME = os.environ.get("SENDGRID_USERNAME", None) # noqa: F405
SENDGRID_PASSWORD = os.environ.get('SENDGRID_PASSWORD', None) # noqa: F405 SENDGRID_PASSWORD = os.environ.get("SENDGRID_PASSWORD", None) # noqa: F405
# Use SendGrid if we have the addon installed, else just print to console which # Use SendGrid if we have the addon installed, else just print to console which
# is accessible via Heroku logs # is accessible via Heroku logs
if SENDGRID_USERNAME and SENDGRID_PASSWORD: if SENDGRID_USERNAME and SENDGRID_PASSWORD:
EMAIL_HOST = 'smtp.sendgrid.net' EMAIL_HOST = "smtp.sendgrid.net"
EMAIL_HOST_USER = SENDGRID_USERNAME EMAIL_HOST_USER = SENDGRID_USERNAME
EMAIL_HOST_PASSWORD = SENDGRID_PASSWORD EMAIL_HOST_PASSWORD = SENDGRID_PASSWORD
EMAIL_PORT = 587 EMAIL_PORT = 587
EMAIL_USE_TLS = True EMAIL_USE_TLS = True
EMAIL_TIMEOUT = 60 EMAIL_TIMEOUT = 60
else: else:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

View File

@ -3,24 +3,24 @@ from .base import *
# Production settings # Production settings
# See babybuddy.settings.base for additional settings information. # See babybuddy.settings.base for additional settings information.
SECRET_KEY = '' SECRET_KEY = ""
ALLOWED_HOSTS = [''] ALLOWED_HOSTS = [""]
# Database # Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases # https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.sqlite3', "ENGINE": "django.db.backends.sqlite3",
'NAME': os.path.join(BASE_DIR, '../data/db.sqlite3'), "NAME": os.path.join(BASE_DIR, "../data/db.sqlite3"),
} }
} }
# Media files # Media files
# https://docs.djangoproject.com/en/3.0/topics/files/ # https://docs.djangoproject.com/en/3.0/topics/files/
MEDIA_ROOT = os.path.join(BASE_DIR, '../data/media') MEDIA_ROOT = os.path.join(BASE_DIR, "../data/media")
# Security # Security
# After setting up SSL, uncomment the settings below for enhanced security of # After setting up SSL, uncomment the settings below for enhanced security of

View File

@ -1,13 +1,13 @@
from .base import * from .base import *
SECRET_KEY = 'TESTS' SECRET_KEY = "TESTS"
# Password hasher configuration # Password hasher configuration
# See https://docs.djangoproject.com/en/3.2/ref/settings/#password-hashers # See https://docs.djangoproject.com/en/3.2/ref/settings/#password-hashers
# See https://docs.djangoproject.com/en/3.2/topics/testing/overview/#password-hashing # See https://docs.djangoproject.com/en/3.2/topics/testing/overview/#password-hashing
PASSWORD_HASHERS = [ PASSWORD_HASHERS = [
'django.contrib.auth.hashers.MD5PasswordHasher', "django.contrib.auth.hashers.MD5PasswordHasher",
] ]
# Axes configuration # Axes configuration

View File

@ -19,12 +19,11 @@ def relative_url(context, field_name, value):
:param value: the new value for field_name. :param value: the new value for field_name.
:return: encoded relative url with updated query string. :return: encoded relative url with updated query string.
""" """
url = '?{}={}'.format(field_name, value) url = "?{}={}".format(field_name, value)
querystring = context['request'].GET.urlencode().split('&') querystring = context["request"].GET.urlencode().split("&")
filtered_querystring = filter( filtered_querystring = filter(lambda p: p.split("=")[0] != field_name, querystring)
lambda p: p.split('=')[0] != field_name, querystring) encoded_querystring = "&".join(filtered_querystring)
encoded_querystring = '&'.join(filtered_querystring) return "{}&{}".format(url, encoded_querystring)
return '{}&{}'.format(url, encoded_querystring)
@register.simple_tag() @register.simple_tag()
@ -34,7 +33,7 @@ def version_string():
:return: version string ('n.n.n (commit)'). :return: version string ('n.n.n (commit)').
""" """
config = apps.get_app_config('babybuddy') config = apps.get_app_config("babybuddy")
return config.version_string return config.version_string

View File

@ -10,16 +10,16 @@ from babybuddy.middleware import update_en_gb_date_formats
class GbFormatsTestCase(TestCase): class GbFormatsTestCase(TestCase):
@override_settings(LANGUAGE_CODE='en-GB') @override_settings(LANGUAGE_CODE="en-GB")
def test_datetime_input_formats(self): def test_datetime_input_formats(self):
update_en_gb_date_formats() update_en_gb_date_formats()
field = DateTimeField() field = DateTimeField()
supported_custom_examples = [ supported_custom_examples = [
'20/01/2020', "20/01/2020",
'20/01/2020 9:30 AM', "20/01/2020 9:30 AM",
'20/01/2020 9:30:03 AM', "20/01/2020 9:30:03 AM",
'01/10/2020 11:30 PM', "01/10/2020 11:30 PM",
'01/10/2020 11:30:03 AM', "01/10/2020 11:30:03 AM",
] ]
for example in supported_custom_examples: for example in supported_custom_examples:
@ -30,18 +30,18 @@ class GbFormatsTestCase(TestCase):
self.fail('Format of "{}" not recognized!'.format(example)) self.fail('Format of "{}" not recognized!'.format(example))
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
field.to_python('invalid date string!') field.to_python("invalid date string!")
# @tag('isolate') # @tag('isolate')
@override_settings(LANGUAGE_CODE='en-GB', USE_24_HOUR_TIME_FORMAT=True) @override_settings(LANGUAGE_CODE="en-GB", USE_24_HOUR_TIME_FORMAT=True)
def test_use_24_hour_time_format(self): def test_use_24_hour_time_format(self):
update_en_gb_date_formats() update_en_gb_date_formats()
field = DateTimeField() field = DateTimeField()
supported_custom_examples = [ supported_custom_examples = [
'25/10/2006 2:30:59', "25/10/2006 2:30:59",
'25/10/2006 2:30', "25/10/2006 2:30",
'25/10/2006 14:30:59', "25/10/2006 14:30:59",
'25/10/2006 14:30', "25/10/2006 14:30",
] ]
for example in supported_custom_examples: for example in supported_custom_examples:
@ -52,20 +52,16 @@ class GbFormatsTestCase(TestCase):
self.fail('Format of "{}" not recognized!'.format(example)) self.fail('Format of "{}" not recognized!'.format(example))
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
field.to_python('invalid date string!') field.to_python("invalid date string!")
dt = datetime.datetime(year=2011, month=11, day=4, hour=23, minute=5, dt = datetime.datetime(year=2011, month=11, day=4, hour=23, minute=5, second=59)
second=59) self.assertEqual(date_format(dt, "DATETIME_FORMAT"), "4 November 2011 23:05:59")
self.assertEqual(
date_format(dt, 'DATETIME_FORMAT'), '4 November 2011 23:05:59')
dt = datetime.datetime(year=2011, month=11, day=4, hour=2, minute=5, dt = datetime.datetime(year=2011, month=11, day=4, hour=2, minute=5, second=59)
second=59) self.assertEqual(date_format(dt, "SHORT_DATETIME_FORMAT"), "04/11/2011 02:05")
self.assertEqual(
date_format(dt, 'SHORT_DATETIME_FORMAT'), '04/11/2011 02:05')
t = datetime.time(hour=16, minute=2, second=25) t = datetime.time(hour=16, minute=2, second=25)
self.assertEqual(time_format(t), '16:02') self.assertEqual(time_format(t), "16:02")
# def test_short_month_day_format(self): # def test_short_month_day_format(self):
# update_en_gb_date_formats() # update_en_gb_date_formats()

View File

@ -14,10 +14,10 @@ class FormatsTestCase(TestCase):
update_en_us_date_formats() update_en_us_date_formats()
field = DateTimeField() field = DateTimeField()
supported_custom_examples = [ supported_custom_examples = [
'01/20/2020 9:30 AM', "01/20/2020 9:30 AM",
'01/20/2020 9:30:03 AM', "01/20/2020 9:30:03 AM",
'10/01/2020 11:30 PM', "10/01/2020 11:30 PM",
'10/01/2020 11:30:03 AM', "10/01/2020 11:30:03 AM",
] ]
for example in supported_custom_examples: for example in supported_custom_examples:
@ -28,18 +28,18 @@ class FormatsTestCase(TestCase):
self.fail('Format of "{}" not recognized!'.format(example)) self.fail('Format of "{}" not recognized!'.format(example))
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
field.to_python('invalid date string!') field.to_python("invalid date string!")
@tag('isolate') @tag("isolate")
@override_settings(LANGUAGE_CODE='en-US', USE_24_HOUR_TIME_FORMAT=True) @override_settings(LANGUAGE_CODE="en-US", USE_24_HOUR_TIME_FORMAT=True)
def test_use_24_hour_time_format(self): def test_use_24_hour_time_format(self):
update_en_us_date_formats() update_en_us_date_formats()
field = DateTimeField() field = DateTimeField()
supported_custom_examples = [ supported_custom_examples = [
'10/25/2006 2:30:59', "10/25/2006 2:30:59",
'10/25/2006 2:30', "10/25/2006 2:30",
'10/25/2006 14:30:59', "10/25/2006 14:30:59",
'10/25/2006 14:30', "10/25/2006 14:30",
] ]
for example in supported_custom_examples: for example in supported_custom_examples:
@ -50,23 +50,18 @@ class FormatsTestCase(TestCase):
self.fail('Format of "{}" not recognized!'.format(example)) self.fail('Format of "{}" not recognized!'.format(example))
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
field.to_python('invalid date string!') field.to_python("invalid date string!")
dt = datetime.datetime(year=2011, month=11, day=4, hour=23, minute=5, dt = datetime.datetime(year=2011, month=11, day=4, hour=23, minute=5, second=59)
second=59) self.assertEqual(date_format(dt, "DATETIME_FORMAT"), "Nov. 4, 2011, 23:05:59")
self.assertEqual(
date_format(dt, 'DATETIME_FORMAT'), 'Nov. 4, 2011, 23:05:59')
dt = datetime.datetime(year=2011, month=11, day=4, hour=2, minute=5, dt = datetime.datetime(year=2011, month=11, day=4, hour=2, minute=5, second=59)
second=59) self.assertEqual(date_format(dt, "SHORT_DATETIME_FORMAT"), "11/04/2011 2:05:59")
self.assertEqual(
date_format(dt, 'SHORT_DATETIME_FORMAT'), '11/04/2011 2:05:59')
t = datetime.time(hour=16, minute=2, second=25) t = datetime.time(hour=16, minute=2, second=25)
self.assertEqual(time_format(t), '16:02:25') self.assertEqual(time_format(t), "16:02:25")
def test_short_month_day_format(self): def test_short_month_day_format(self):
update_en_us_date_formats() update_en_us_date_formats()
dt = datetime.datetime(year=2021, month=7, day=31, hour=5, minute=5, dt = datetime.datetime(year=2021, month=7, day=31, hour=5, minute=5, second=5)
second=5) self.assertEqual(date_format(dt, "SHORT_MONTH_DAY_FORMAT"), "Jul 31")
self.assertEqual(date_format(dt, 'SHORT_MONTH_DAY_FORMAT'), 'Jul 31')

View File

@ -8,17 +8,17 @@ from core.models import Child
class CommandsTestCase(TransactionTestCase): class CommandsTestCase(TransactionTestCase):
def test_migrate(self): def test_migrate(self):
call_command('migrate', verbosity=0) call_command("migrate", verbosity=0)
self.assertIsInstance(User.objects.get(username='admin'), User) self.assertIsInstance(User.objects.get(username="admin"), User)
def test_fake(self): def test_fake(self):
call_command('migrate', verbosity=0) call_command("migrate", verbosity=0)
call_command('fake', children=1, days=7, verbosity=0) call_command("fake", children=1, days=7, verbosity=0)
self.assertEqual(Child.objects.count(), 1) self.assertEqual(Child.objects.count(), 1)
call_command('fake', children=2, days=7, verbosity=0) call_command("fake", children=2, days=7, verbosity=0)
self.assertEqual(Child.objects.count(), 3) self.assertEqual(Child.objects.count(), 3)
def test_reset(self): def test_reset(self):
call_command('reset', verbosity=0, interactive=False) call_command("reset", verbosity=0, interactive=False)
self.assertIsInstance(User.objects.get(username='admin'), User) self.assertIsInstance(User.objects.get(username="admin"), User)
self.assertEqual(Child.objects.count(), 1) self.assertEqual(Child.objects.count(), 1)

View File

@ -14,55 +14,58 @@ class FormsTestCase(TestCase):
def setUpClass(cls): def setUpClass(cls):
super(FormsTestCase, cls).setUpClass() super(FormsTestCase, cls).setUpClass()
fake = Factory.create() fake = Factory.create()
call_command('migrate', verbosity=0) call_command("migrate", verbosity=0)
call_command('fake', verbosity=0) call_command("fake", verbosity=0)
cls.c = HttpClient() cls.c = HttpClient()
fake_user = fake.simple_profile() fake_user = fake.simple_profile()
cls.credentials = { cls.credentials = {
'username': fake_user['username'], "username": fake_user["username"],
'password': fake.password() "password": fake.password(),
} }
cls.user = User.objects.create_user( cls.user = User.objects.create_user(is_superuser=True, **cls.credentials)
is_superuser=True, **cls.credentials)
cls.settings_template = { cls.settings_template = {
'first_name': 'User', "first_name": "User",
'last_name': 'Name', "last_name": "Name",
'email': 'user@user.user', "email": "user@user.user",
'dashboard_refresh_rate': '', "dashboard_refresh_rate": "",
'language': 'en-US', "language": "en-US",
'timezone': 'UTC', "timezone": "UTC",
'next': '/user/settings/' "next": "/user/settings/",
} }
def test_change_password(self): def test_change_password(self):
self.c.login(**self.credentials) self.c.login(**self.credentials)
page = self.c.get('/user/password/') page = self.c.get("/user/password/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
params = { params = {
'old_password': 'wrong', "old_password": "wrong",
'new_password1': 'mynewpassword', "new_password1": "mynewpassword",
'new_password2': 'notmynewpassword' "new_password2": "notmynewpassword",
} }
page = self.c.post('/user/password/', params) page = self.c.post("/user/password/", params)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertFormError(page, 'form', 'old_password', self.assertFormError(
'Your old password was entered incorrectly. ' page,
'Please enter it again.') "form",
"old_password",
"Your old password was entered incorrectly. " "Please enter it again.",
)
params['old_password'] = self.credentials['password'] params["old_password"] = self.credentials["password"]
page = self.c.post('/user/password/', params) page = self.c.post("/user/password/", params)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertFormError(page, 'form', 'new_password2', self.assertFormError(
"The two password fields didnt match.") page, "form", "new_password2", "The two password fields didnt match."
)
params['new_password2'] = 'mynewpassword' params["new_password2"] = "mynewpassword"
page = self.c.post('/user/password/', params) page = self.c.post("/user/password/", params)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
def test_user_forms(self): def test_user_forms(self):
@ -71,38 +74,38 @@ class FormsTestCase(TestCase):
self.c.login(**self.credentials) self.c.login(**self.credentials)
params = { params = {
'username': 'username', "username": "username",
'first_name': 'User', "first_name": "User",
'last_name': 'Name', "last_name": "Name",
'email': 'user@user.user', "email": "user@user.user",
'password1': 'd47o8dD&#hu3ulu3', "password1": "d47o8dD&#hu3ulu3",
'password2': 'd47o8dD&#hu3ulu3' "password2": "d47o8dD&#hu3ulu3",
} }
page = self.c.post('/users/add/', params) page = self.c.post("/users/add/", params)
self.assertEqual(page.status_code, 302) self.assertEqual(page.status_code, 302)
new_user = User.objects.get(username='username') new_user = User.objects.get(username="username")
self.assertIsInstance(new_user, User) self.assertIsInstance(new_user, User)
params['first_name'] = 'Changed' params["first_name"] = "Changed"
page = self.c.post('/users/{}/edit/'.format(new_user.id), params) page = self.c.post("/users/{}/edit/".format(new_user.id), params)
self.assertEqual(page.status_code, 302) self.assertEqual(page.status_code, 302)
new_user.refresh_from_db() new_user.refresh_from_db()
self.assertEqual(new_user.first_name, params['first_name']) self.assertEqual(new_user.first_name, params["first_name"])
page = self.c.post('/users/{}/delete/'.format(new_user.id)) page = self.c.post("/users/{}/delete/".format(new_user.id))
self.assertEqual(page.status_code, 302) self.assertEqual(page.status_code, 302)
self.assertQuerysetEqual(User.objects.filter(username='username'), []) self.assertQuerysetEqual(User.objects.filter(username="username"), [])
def test_user_settings(self): def test_user_settings(self):
self.c.login(**self.credentials) self.c.login(**self.credentials)
params = self.settings_template.copy() params = self.settings_template.copy()
params['first_name'] = 'New First Name' params["first_name"] = "New First Name"
page = self.c.post('/user/settings/', params, follow=True) page = self.c.post("/user/settings/", params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertContains(page, 'New First Name') self.assertContains(page, "New First Name")
def test_user_regenerate_api_key(self): def test_user_regenerate_api_key(self):
self.c.login(**self.credentials) self.c.login(**self.credentials)
@ -110,53 +113,50 @@ class FormsTestCase(TestCase):
api_key_before = User.objects.get(pk=self.user.id).settings.api_key() api_key_before = User.objects.get(pk=self.user.id).settings.api_key()
params = self.settings_template.copy() params = self.settings_template.copy()
params['api_key_regenerate'] = 'Regenerate' params["api_key_regenerate"] = "Regenerate"
page = self.c.post('/user/settings/', params, follow=True) page = self.c.post("/user/settings/", params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertNotEqual( self.assertNotEqual(
api_key_before, api_key_before, User.objects.get(pk=self.user.id).settings.api_key()
User.objects.get(pk=self.user.id).settings.api_key()
) )
def test_user_settings_invalid(self): def test_user_settings_invalid(self):
self.c.login(**self.credentials) self.c.login(**self.credentials)
params = self.settings_template.copy() params = self.settings_template.copy()
params['email'] = 'Not an email address' params["email"] = "Not an email address"
page = self.c.post('/user/settings/', params) page = self.c.post("/user/settings/", params)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertFormError(page, 'user_form', 'email', self.assertFormError(page, "user_form", "email", "Enter a valid email address.")
'Enter a valid email address.')
def test_user_settings_language(self): def test_user_settings_language(self):
self.c.login(**self.credentials) self.c.login(**self.credentials)
params = self.settings_template.copy() params = self.settings_template.copy()
params['language'] = 'fr' params["language"] = "fr"
page = self.c.post('/user/settings/', data=params, follow=True) page = self.c.post("/user/settings/", data=params, follow=True)
self.assertContains(page, 'Paramètres Utilisateur') self.assertContains(page, "Paramètres Utilisateur")
@override_settings(TIME_ZONE='US/Eastern') @override_settings(TIME_ZONE="US/Eastern")
def test_user_settings_timezone(self): def test_user_settings_timezone(self):
self.c.login(**self.credentials) self.c.login(**self.credentials)
self.assertEqual(timezone.get_default_timezone_name(), 'US/Eastern') self.assertEqual(timezone.get_default_timezone_name(), "US/Eastern")
params = self.settings_template.copy() params = self.settings_template.copy()
params['timezone'] = 'US/Pacific' params["timezone"] = "US/Pacific"
page = self.c.post('/user/settings/', data=params, follow=True) page = self.c.post("/user/settings/", data=params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertEqual(timezone.get_current_timezone_name(), self.assertEqual(timezone.get_current_timezone_name(), params["timezone"])
params['timezone'])
def test_user_settings_dashboard_hide_empty_on(self): def test_user_settings_dashboard_hide_empty_on(self):
self.c.login(**self.credentials) self.c.login(**self.credentials)
params = self.settings_template.copy() params = self.settings_template.copy()
params['dashboard_hide_empty'] = 'on' params["dashboard_hide_empty"] = "on"
page = self.c.post('/user/settings/', data=params, follow=True) page = self.c.post("/user/settings/", data=params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.user.refresh_from_db() self.user.refresh_from_db()
self.assertTrue(self.user.settings.dashboard_hide_empty) self.assertTrue(self.user.settings.dashboard_hide_empty)
@ -165,22 +165,24 @@ class FormsTestCase(TestCase):
self.c.login(**self.credentials) self.c.login(**self.credentials)
params = self.settings_template.copy() params = self.settings_template.copy()
params['dashboard_refresh_rate'] = '0:05:00' params["dashboard_refresh_rate"] = "0:05:00"
page = self.c.post('/user/settings/', data=params, follow=True) page = self.c.post("/user/settings/", data=params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.user.refresh_from_db() self.user.refresh_from_db()
self.assertEqual(self.user.settings.dashboard_refresh_rate, self.assertEqual(
datetime.timedelta(seconds=300)) self.user.settings.dashboard_refresh_rate, datetime.timedelta(seconds=300)
)
def test_user_settings_dashboard_hide_age(self): def test_user_settings_dashboard_hide_age(self):
self.c.login(**self.credentials) self.c.login(**self.credentials)
params = self.settings_template.copy() params = self.settings_template.copy()
params['dashboard_hide_age'] = '1 day, 0:00:00' params["dashboard_hide_age"] = "1 day, 0:00:00"
page = self.c.post('/user/settings/', data=params, follow=True) page = self.c.post("/user/settings/", data=params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.user.refresh_from_db() self.user.refresh_from_db()
self.assertEqual(self.user.settings.dashboard_hide_age, self.assertEqual(
datetime.timedelta(days=1)) self.user.settings.dashboard_hide_age, datetime.timedelta(days=1)
)

View File

@ -8,22 +8,18 @@ from babybuddy.models import Settings
class SettingsTestCase(TestCase): class SettingsTestCase(TestCase):
def setUp(self): def setUp(self):
call_command('migrate', verbosity=0) call_command("migrate", verbosity=0)
def test_settings(self): def test_settings(self):
credentials = { credentials = {"username": "Test", "password": "User"}
'username': 'Test',
'password': 'User'
}
user = User.objects.create_user(is_superuser=True, **credentials) user = User.objects.create_user(is_superuser=True, **credentials)
self.assertIsInstance(user.settings, Settings) self.assertIsInstance(user.settings, Settings)
self.assertEqual( self.assertEqual(user.settings.dashboard_refresh_rate_milliseconds, 60000)
user.settings.dashboard_refresh_rate_milliseconds, 60000)
user.settings.dashboard_refresh_rate = None user.settings.dashboard_refresh_rate = None
user.save() user.save()
self.assertIsNone(user.settings.dashboard_refresh_rate_milliseconds) self.assertIsNone(user.settings.dashboard_refresh_rate_milliseconds)
user.settings.language = 'fr' user.settings.language = "fr"
user.save() user.save()
self.assertEqual(user.settings.language, 'fr') self.assertEqual(user.settings.language, "fr")

View File

@ -9,9 +9,11 @@ from babybuddy.templatetags import babybuddy_tags
class TemplateTagsTestCase(TestCase): class TemplateTagsTestCase(TestCase):
def test_child_count(self): def test_child_count(self):
self.assertEqual(babybuddy_tags.get_child_count(), 0) self.assertEqual(babybuddy_tags.get_child_count(), 0)
Child.objects.create(first_name='Test', last_name='Child', Child.objects.create(
birth_date=timezone.localdate()) first_name="Test", last_name="Child", birth_date=timezone.localdate()
)
self.assertEqual(babybuddy_tags.get_child_count(), 1) self.assertEqual(babybuddy_tags.get_child_count(), 1)
Child.objects.create(first_name='Test', last_name='Child 2', Child.objects.create(
birth_date=timezone.localdate()) first_name="Test", last_name="Child 2", birth_date=timezone.localdate()
)
self.assertEqual(babybuddy_tags.get_child_count(), 2) self.assertEqual(babybuddy_tags.get_child_count(), 2)

View File

@ -14,63 +14,62 @@ class ViewsTestCase(TestCase):
def setUpClass(cls): def setUpClass(cls):
super(ViewsTestCase, cls).setUpClass() super(ViewsTestCase, cls).setUpClass()
fake = Factory.create() fake = Factory.create()
call_command('migrate', verbosity=0) call_command("migrate", verbosity=0)
cls.c = HttpClient() cls.c = HttpClient()
fake_user = fake.simple_profile() fake_user = fake.simple_profile()
cls.credentials = { cls.credentials = {
'username': fake_user['username'], "username": fake_user["username"],
'password': fake.password() "password": fake.password(),
} }
cls.user = User.objects.create_user( cls.user = User.objects.create_user(is_superuser=True, **cls.credentials)
is_superuser=True, **cls.credentials)
cls.c.login(**cls.credentials) cls.c.login(**cls.credentials)
def test_root_router(self): def test_root_router(self):
page = self.c.get('/') page = self.c.get("/")
self.assertEqual(page.url, '/dashboard/') self.assertEqual(page.url, "/dashboard/")
@override_settings(ROLLING_SESSION_REFRESH=1) @override_settings(ROLLING_SESSION_REFRESH=1)
def test_rolling_sessions(self): def test_rolling_sessions(self):
self.c.get('/') self.c.get("/")
session1 = str(self.c.cookies['sessionid']) session1 = str(self.c.cookies["sessionid"])
# Sleep longer than ROLLING_SESSION_REFRESH. # Sleep longer than ROLLING_SESSION_REFRESH.
time.sleep(2) time.sleep(2)
self.c.get('/') self.c.get("/")
session2 = str(self.c.cookies['sessionid']) session2 = str(self.c.cookies["sessionid"])
self.c.get('/') self.c.get("/")
session3 = str(self.c.cookies['sessionid']) session3 = str(self.c.cookies["sessionid"])
self.assertNotEqual(session1, session2) self.assertNotEqual(session1, session2)
self.assertEqual(session2, session3) self.assertEqual(session2, session3)
def test_user_settings(self): def test_user_settings(self):
page = self.c.get('/user/settings/') page = self.c.get("/user/settings/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
def test_user_views(self): def test_user_views(self):
# Staff setting is required to access user management. # Staff setting is required to access user management.
page = self.c.get('/users/') page = self.c.get("/users/")
self.assertEqual(page.status_code, 403) self.assertEqual(page.status_code, 403)
self.user.is_staff = True self.user.is_staff = True
self.user.save() self.user.save()
page = self.c.get('/users/') page = self.c.get("/users/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/users/add/') page = self.c.get("/users/add/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
entry = User.objects.first() entry = User.objects.first()
page = self.c.get('/users/{}/edit/'.format(entry.id)) page = self.c.get("/users/{}/edit/".format(entry.id))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/users/{}/delete/'.format(entry.id)) page = self.c.get("/users/{}/delete/".format(entry.id))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
def test_welcome(self): def test_welcome(self):
page = self.c.get('/welcome/') page = self.c.get("/welcome/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
def test_logout_get_fails(self): def test_logout_get_fails(self):
page = self.c.get('/logout/') page = self.c.get("/logout/")
self.assertEqual(page.status_code, 405) self.assertEqual(page.status_code, 405)

View File

@ -8,54 +8,30 @@ from django.urls import include, path
from . import views from . import views
app_patterns = [ app_patterns = [
path('login/', auth_views.LoginView.as_view(), name='login'), path("login/", auth_views.LoginView.as_view(), name="login"),
path('logout/', views.LogoutView.as_view(), name='logout'), path("logout/", views.LogoutView.as_view(), name="logout"),
path( path(
'password_reset/', "password_reset/", auth_views.PasswordResetView.as_view(), name="password_reset"
auth_views.PasswordResetView.as_view(),
name='password_reset'
),
path('', views.RootRouter.as_view(), name='root-router'),
path('welcome/', views.Welcome.as_view(), name='welcome'),
path('users/', views.UserList.as_view(), name='user-list'),
path('users/add/', views.UserAdd.as_view(), name='user-add'),
path(
'users/<int:pk>/edit/',
views.UserUpdate.as_view(),
name='user-update'
),
path(
'users/<int:pk>/delete/',
views.UserDelete.as_view(),
name='user-delete'
),
path(
'user/password/',
views.UserPassword.as_view(),
name='user-password'
),
path(
'user/settings/',
views.UserSettings.as_view(),
name='user-settings'
), ),
path("", views.RootRouter.as_view(), name="root-router"),
path("welcome/", views.Welcome.as_view(), name="welcome"),
path("users/", views.UserList.as_view(), name="user-list"),
path("users/add/", views.UserAdd.as_view(), name="user-add"),
path("users/<int:pk>/edit/", views.UserUpdate.as_view(), name="user-update"),
path("users/<int:pk>/delete/", views.UserDelete.as_view(), name="user-delete"),
path("user/password/", views.UserPassword.as_view(), name="user-password"),
path("user/settings/", views.UserSettings.as_view(), name="user-settings"),
] ]
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path("admin/", admin.site.urls),
path('', include('api.urls', namespace='api')), path("", include("api.urls", namespace="api")),
path('', include((app_patterns, 'babybuddy'), namespace='babybuddy')), path("", include((app_patterns, "babybuddy"), namespace="babybuddy")),
path('user/lang', include('django.conf.urls.i18n')), path("user/lang", include("django.conf.urls.i18n")),
path('', include('core.urls', namespace='core')), path("", include("core.urls", namespace="core")),
path('', include('dashboard.urls', namespace='dashboard')), path("", include("dashboard.urls", namespace="dashboard")),
path('', include('reports.urls', namespace='reports')), path("", include("reports.urls", namespace="reports")),
] ]
if settings.DEBUG: # pragma: no cover if settings.DEBUG: # pragma: no cover
urlpatterns += static( urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT
)

View File

@ -22,16 +22,16 @@ from django.views.i18n import set_language
from django_filters.views import FilterView from django_filters.views import FilterView
from babybuddy import forms from babybuddy import forms
from babybuddy.mixins import LoginRequiredMixin, PermissionRequiredMixin, \ from babybuddy.mixins import LoginRequiredMixin, PermissionRequiredMixin, StaffOnlyMixin
StaffOnlyMixin
class RootRouter(LoginRequiredMixin, RedirectView): class RootRouter(LoginRequiredMixin, RedirectView):
""" """
Redirects to the site dashboard. Redirects to the site dashboard.
""" """
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
self.url = reverse('dashboard:dashboard') self.url = reverse("dashboard:dashboard")
return super(RootRouter, self).get_redirect_url(self, *args, **kwargs) return super(RootRouter, self).get_redirect_url(self, *args, **kwargs)
@ -40,86 +40,85 @@ class BabyBuddyFilterView(FilterView):
Disables "strictness" for django-filter. It is unclear from the Disables "strictness" for django-filter. It is unclear from the
documentation exactly what this does... documentation exactly what this does...
""" """
# TODO Figure out the correct way to use this. # TODO Figure out the correct way to use this.
strict = False strict = False
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
children = { children = {o.child for o in context["object_list"] if hasattr(o, "child")}
o.child for o in context['object_list'] if hasattr(o, "child")
}
if len(children) == 1: if len(children) == 1:
context['unique_child'] = True context["unique_child"] = True
return context return context
@method_decorator(csrf_protect, name='dispatch') @method_decorator(csrf_protect, name="dispatch")
@method_decorator(never_cache, name='dispatch') @method_decorator(never_cache, name="dispatch")
@method_decorator(require_POST, name='dispatch') @method_decorator(require_POST, name="dispatch")
class LogoutView(LogoutViewBase): class LogoutView(LogoutViewBase):
pass pass
class UserList(StaffOnlyMixin, BabyBuddyFilterView): class UserList(StaffOnlyMixin, BabyBuddyFilterView):
model = User model = User
template_name = 'babybuddy/user_list.html' template_name = "babybuddy/user_list.html"
ordering = 'username' ordering = "username"
paginate_by = 10 paginate_by = 10
filterset_fields = ('username', 'first_name', 'last_name', 'email') filterset_fields = ("username", "first_name", "last_name", "email")
class UserAdd(StaffOnlyMixin, PermissionRequiredMixin, SuccessMessageMixin, class UserAdd(StaffOnlyMixin, PermissionRequiredMixin, SuccessMessageMixin, CreateView):
CreateView):
model = User model = User
template_name = 'babybuddy/user_form.html' template_name = "babybuddy/user_form.html"
permission_required = ('admin.add_user',) permission_required = ("admin.add_user",)
form_class = forms.UserAddForm form_class = forms.UserAddForm
success_url = reverse_lazy('babybuddy:user-list') success_url = reverse_lazy("babybuddy:user-list")
success_message = gettext_lazy('User %(username)s added!') success_message = gettext_lazy("User %(username)s added!")
class UserUpdate(StaffOnlyMixin, PermissionRequiredMixin, class UserUpdate(
SuccessMessageMixin, UpdateView): StaffOnlyMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView
):
model = User model = User
template_name = 'babybuddy/user_form.html' template_name = "babybuddy/user_form.html"
permission_required = ('admin.change_user',) permission_required = ("admin.change_user",)
form_class = forms.UserUpdateForm form_class = forms.UserUpdateForm
success_url = reverse_lazy('babybuddy:user-list') success_url = reverse_lazy("babybuddy:user-list")
success_message = gettext_lazy('User %(username)s updated.') success_message = gettext_lazy("User %(username)s updated.")
class UserDelete(StaffOnlyMixin, PermissionRequiredMixin, class UserDelete(
DeleteView, SuccessMessageMixin): StaffOnlyMixin, PermissionRequiredMixin, DeleteView, SuccessMessageMixin
):
model = User model = User
template_name = 'babybuddy/user_confirm_delete.html' template_name = "babybuddy/user_confirm_delete.html"
permission_required = ('admin.delete_user',) permission_required = ("admin.delete_user",)
success_url = reverse_lazy('babybuddy:user-list') success_url = reverse_lazy("babybuddy:user-list")
def get_success_message(self, cleaned_data): def get_success_message(self, cleaned_data):
return format_lazy(gettext_lazy( return format_lazy(gettext_lazy("User {user} deleted."), user=self.get_object())
'User {user} deleted.'), user=self.get_object()
)
class UserPassword(LoginRequiredMixin, View): class UserPassword(LoginRequiredMixin, View):
""" """
Handles user password changes. Handles user password changes.
""" """
form_class = forms.UserPasswordForm form_class = forms.UserPasswordForm
template_name = 'babybuddy/user_password_form.html' template_name = "babybuddy/user_password_form.html"
def get(self, request): def get(self, request):
return render(request, self.template_name, { return render(
'form': self.form_class(request.user) request, self.template_name, {"form": self.form_class(request.user)}
}) )
def post(self, request): def post(self, request):
form = PasswordChangeForm(request.user, request.POST) form = PasswordChangeForm(request.user, request.POST)
if form.is_valid(): if form.is_valid():
user = form.save() user = form.save()
update_session_auth_hash(request, user) update_session_auth_hash(request, user)
messages.success(request, _('Password updated.')) messages.success(request, _("Password updated."))
return render(request, self.template_name, {'form': form}) return render(request, self.template_name, {"form": form})
class UserSettings(LoginRequiredMixin, View): class UserSettings(LoginRequiredMixin, View):
@ -127,42 +126,47 @@ class UserSettings(LoginRequiredMixin, View):
Handles both the User and Settings models. Handles both the User and Settings models.
Based on this SO answer: https://stackoverflow.com/a/45056835. Based on this SO answer: https://stackoverflow.com/a/45056835.
""" """
form_user_class = forms.UserForm form_user_class = forms.UserForm
form_settings_class = forms.UserSettingsForm form_settings_class = forms.UserSettingsForm
template_name = 'babybuddy/user_settings_form.html' template_name = "babybuddy/user_settings_form.html"
def get(self, request): def get(self, request):
return render(request, self.template_name, { return render(
'form_user': self.form_user_class(instance=request.user), request,
'form_settings': self.form_settings_class( self.template_name,
instance=request.user.settings) {
}) "form_user": self.form_user_class(instance=request.user),
"form_settings": self.form_settings_class(
instance=request.user.settings
),
},
)
def post(self, request): def post(self, request):
if request.POST.get('api_key_regenerate'): if request.POST.get("api_key_regenerate"):
request.user.settings.api_key(reset=True) request.user.settings.api_key(reset=True)
messages.success(request, _('User API key regenerated.')) messages.success(request, _("User API key regenerated."))
return redirect('babybuddy:user-settings') return redirect("babybuddy:user-settings")
form_user = self.form_user_class( form_user = self.form_user_class(instance=request.user, data=request.POST)
instance=request.user,
data=request.POST)
form_settings = self.form_settings_class( form_settings = self.form_settings_class(
instance=request.user.settings, instance=request.user.settings, data=request.POST
data=request.POST) )
if form_user.is_valid() and form_settings.is_valid(): if form_user.is_valid() and form_settings.is_valid():
user = form_user.save(commit=False) user = form_user.save(commit=False)
user_settings = form_settings.save(commit=False) user_settings = form_settings.save(commit=False)
user.settings = user_settings user.settings = user_settings
user.save() user.save()
translation.activate(user.settings.language) translation.activate(user.settings.language)
messages.success(request, _('Settings saved!')) messages.success(request, _("Settings saved!"))
translation.deactivate() translation.deactivate()
return set_language(request) return set_language(request)
return render(request, self.template_name, { return render(
'user_form': form_user, request,
'settings_form': form_settings self.template_name,
}) {"user_form": form_user, "settings_form": form_settings},
)
class Welcome(LoginRequiredMixin, TemplateView): class Welcome(LoginRequiredMixin, TemplateView):
@ -170,4 +174,5 @@ class Welcome(LoginRequiredMixin, TemplateView):
Basic introduction to Baby Buddy (meant to be shown when no data is in the Basic introduction to Baby Buddy (meant to be shown when no data is in the
database). database).
""" """
template_name = 'babybuddy/welcome.html'
template_name = "babybuddy/welcome.html"

View File

@ -9,31 +9,30 @@ from core import models
class ImportExportResourceBase(resources.ModelResource): class ImportExportResourceBase(resources.ModelResource):
id = fields.Field(attribute='id') id = fields.Field(attribute="id")
child = fields.Field(attribute='child_id', column_name='child_id') child = fields.Field(attribute="child_id", column_name="child_id")
child_first_name = fields.Field( child_first_name = fields.Field(attribute="child__first_name", readonly=True)
attribute='child__first_name', readonly=True) child_last_name = fields.Field(attribute="child__last_name", readonly=True)
child_last_name = fields.Field(attribute='child__last_name', readonly=True)
class Meta: class Meta:
clean_model_instances = True clean_model_instances = True
exclude = ('duration',) exclude = ("duration",)
class ChildImportExportResource(resources.ModelResource): class ChildImportExportResource(resources.ModelResource):
class Meta: class Meta:
model = models.Child model = models.Child
exclude = ('picture', 'slug') exclude = ("picture", "slug")
@admin.register(models.Child) @admin.register(models.Child)
class ChildAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin): class ChildAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin):
list_display = ('first_name', 'last_name', 'birth_date', 'slug') list_display = ("first_name", "last_name", "birth_date", "slug")
list_filter = ('last_name',) list_filter = ("last_name",)
search_fields = ('first_name', 'last_name', 'birth_date') search_fields = ("first_name", "last_name", "birth_date")
fields = ['first_name', 'last_name', 'birth_date'] fields = ["first_name", "last_name", "birth_date"]
if settings.BABY_BUDDY['ALLOW_UPLOADS']: if settings.BABY_BUDDY["ALLOW_UPLOADS"]:
fields.append('picture') fields.append("picture")
resource_class = ChildImportExportResource resource_class = ChildImportExportResource
@ -43,11 +42,13 @@ class DiaperChangeImportExportResource(ImportExportResourceBase):
@admin.register(models.DiaperChange) @admin.register(models.DiaperChange)
class DiaperChangeAdmin(ImportExportMixin, ExportActionMixin, class DiaperChangeAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin):
admin.ModelAdmin): list_display = ("child", "time", "wet", "solid", "color")
list_display = ('child', 'time', 'wet', 'solid', 'color') list_filter = ("child", "wet", "solid", "color")
list_filter = ('child', 'wet', 'solid', 'color') search_fields = (
search_fields = ('child__first_name', 'child__last_name',) "child__first_name",
"child__last_name",
)
resource_class = DiaperChangeImportExportResource resource_class = DiaperChangeImportExportResource
@ -58,11 +59,18 @@ class FeedingImportExportResource(ImportExportResourceBase):
@admin.register(models.Feeding) @admin.register(models.Feeding)
class FeedingAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin): class FeedingAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin):
list_display = ('start', 'end', 'duration', 'child', 'type', 'method', list_display = ("start", "end", "duration", "child", "type", "method", "amount")
'amount') list_filter = (
list_filter = ('child', 'type', 'method',) "child",
search_fields = ('child__first_name', 'child__last_name', 'type', "type",
'method',) "method",
)
search_fields = (
"child__first_name",
"child__last_name",
"type",
"method",
)
resource_class = FeedingImportExportResource resource_class = FeedingImportExportResource
@ -73,9 +81,13 @@ class NoteImportExportResource(ImportExportResourceBase):
@admin.register(models.Note) @admin.register(models.Note)
class NoteAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin): class NoteAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin):
list_display = ('time', 'child', 'note',) list_display = (
list_filter = ('child',) "time",
search_fields = ('child__last_name',) "child",
"note",
)
list_filter = ("child",)
search_fields = ("child__last_name",)
resource_class = NoteImportExportResource resource_class = NoteImportExportResource
@ -86,9 +98,12 @@ class SleepImportExportResource(ImportExportResourceBase):
@admin.register(models.Sleep) @admin.register(models.Sleep)
class SleepAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin): class SleepAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin):
list_display = ('start', 'end', 'duration', 'child', 'nap') list_display = ("start", "end", "duration", "child", "nap")
list_filter = ('child',) list_filter = ("child",)
search_fields = ('child__first_name', 'child__last_name',) search_fields = (
"child__first_name",
"child__last_name",
)
resource_class = SleepImportExportResource resource_class = SleepImportExportResource
@ -99,18 +114,25 @@ class TemperatureImportExportResource(ImportExportResourceBase):
@admin.register(models.Temperature) @admin.register(models.Temperature)
class TemperatureAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin): class TemperatureAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin):
list_display = ('child', 'temperature', 'time',) list_display = (
list_filter = ('child',) "child",
search_fields = ('child__first_name', 'child__last_name', 'temperature',) "temperature",
"time",
)
list_filter = ("child",)
search_fields = (
"child__first_name",
"child__last_name",
"temperature",
)
resource_class = TemperatureImportExportResource resource_class = TemperatureImportExportResource
@admin.register(models.Timer) @admin.register(models.Timer)
class TimerAdmin(admin.ModelAdmin): class TimerAdmin(admin.ModelAdmin):
list_display = ('name', 'child', 'start', 'end', 'duration', 'active', list_display = ("name", "child", "start", "end", "duration", "active", "user")
'user') list_filter = ("child", "active", "user")
list_filter = ('child', 'active', 'user') search_fields = ("child__first_name", "child__last_name", "name", "user")
search_fields = ('child__first_name', 'child__last_name', 'name', 'user')
class TummyTimeImportExportResource(ImportExportResourceBase): class TummyTimeImportExportResource(ImportExportResourceBase):
@ -120,9 +142,19 @@ class TummyTimeImportExportResource(ImportExportResourceBase):
@admin.register(models.TummyTime) @admin.register(models.TummyTime)
class TummyTimeAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin): class TummyTimeAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin):
list_display = ('start', 'end', 'duration', 'child', 'milestone',) list_display = (
list_filter = ('child',) "start",
search_fields = ('child__first_name', 'child__last_name', 'milestone',) "end",
"duration",
"child",
"milestone",
)
list_filter = ("child",)
search_fields = (
"child__first_name",
"child__last_name",
"milestone",
)
resource_class = TummyTimeImportExportResource resource_class = TummyTimeImportExportResource
@ -133,7 +165,15 @@ class WeightImportExportResource(ImportExportResourceBase):
@admin.register(models.Weight) @admin.register(models.Weight)
class WeightAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin): class WeightAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin):
list_display = ('child', 'weight', 'date',) list_display = (
list_filter = ('child',) "child",
search_fields = ('child__first_name', 'child__last_name', 'weight',) "weight",
"date",
)
list_filter = ("child",)
search_fields = (
"child__first_name",
"child__last_name",
"weight",
)
resource_class = WeightImportExportResource resource_class = WeightImportExportResource

View File

@ -17,45 +17,48 @@ def set_initial_values(kwargs, form_type):
""" """
# Never update initial values for existing instance (e.g. edit operation). # Never update initial values for existing instance (e.g. edit operation).
if kwargs.get('instance', None): if kwargs.get("instance", None):
return kwargs return kwargs
# Add the "initial" kwarg if it does not already exist. # Add the "initial" kwarg if it does not already exist.
if not kwargs.get('initial'): if not kwargs.get("initial"):
kwargs.update(initial={}) kwargs.update(initial={})
# Set Child based on `child` kwarg or single Chile database. # Set Child based on `child` kwarg or single Chile database.
child_slug = kwargs.get('child', None) child_slug = kwargs.get("child", None)
if child_slug: if child_slug:
kwargs['initial'].update({ kwargs["initial"].update(
'child': models.Child.objects.filter(slug=child_slug).first(), {
}) "child": models.Child.objects.filter(slug=child_slug).first(),
}
)
elif models.Child.count() == 1: elif models.Child.count() == 1:
kwargs['initial'].update({'child': models.Child.objects.first()}) kwargs["initial"].update({"child": models.Child.objects.first()})
# Set start and end time based on Timer from `timer` kwarg. # Set start and end time based on Timer from `timer` kwarg.
timer_id = kwargs.get('timer', None) timer_id = kwargs.get("timer", None)
if timer_id: if timer_id:
timer = models.Timer.objects.get(id=timer_id) timer = models.Timer.objects.get(id=timer_id)
kwargs['initial'].update({ kwargs["initial"].update(
'timer': timer, {"timer": timer, "start": timer.start, "end": timer.end or timezone.now()}
'start': timer.start, )
'end': timer.end or timezone.now()
})
# Set type and method values for Feeding instance based on last feed. # Set type and method values for Feeding instance based on last feed.
if form_type == FeedingForm and 'child' in kwargs['initial']: if form_type == FeedingForm and "child" in kwargs["initial"]:
last_feeding = models.Feeding.objects.filter( last_feeding = (
child=kwargs['initial']['child']).order_by('end').last() models.Feeding.objects.filter(child=kwargs["initial"]["child"])
.order_by("end")
.last()
)
if last_feeding: if last_feeding:
last_method = last_feeding.method last_method = last_feeding.method
last_feed_args = {'type': last_feeding.type} last_feed_args = {"type": last_feeding.type}
if last_method not in ['left breast', 'right breast']: if last_method not in ["left breast", "right breast"]:
last_feed_args['method'] = last_method last_feed_args["method"] = last_method
kwargs['initial'].update(last_feed_args) kwargs["initial"].update(last_feed_args)
# Remove custom kwargs so they do not interfere with `super` calls. # Remove custom kwargs so they do not interfere with `super` calls.
for key in ['child', 'timer']: for key in ["child", "timer"]:
try: try:
kwargs.pop(key) kwargs.pop(key)
except KeyError: except KeyError:
@ -67,7 +70,7 @@ def set_initial_values(kwargs, form_type):
class CoreModelForm(forms.ModelForm): class CoreModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# Set `timer_id` so the Timer can be stopped in the `save` method. # Set `timer_id` so the Timer can be stopped in the `save` method.
self.timer_id = kwargs.get('timer', None) self.timer_id = kwargs.get("timer", None)
kwargs = set_initial_values(kwargs, type(self)) kwargs = set_initial_values(kwargs, type(self))
super(CoreModelForm, self).__init__(*args, **kwargs) super(CoreModelForm, self).__init__(*args, **kwargs)
@ -85,18 +88,16 @@ class CoreModelForm(forms.ModelForm):
class ChildForm(forms.ModelForm): class ChildForm(forms.ModelForm):
class Meta: class Meta:
model = models.Child model = models.Child
fields = [ fields = ["first_name", "last_name", "birth_date"]
'first_name', if settings.BABY_BUDDY["ALLOW_UPLOADS"]:
'last_name', fields.append("picture")
'birth_date'
]
if settings.BABY_BUDDY['ALLOW_UPLOADS']:
fields.append('picture')
widgets = { widgets = {
'birth_date': forms.DateInput(attrs={ "birth_date": forms.DateInput(
'autocomplete': 'off', attrs={
'data-target': '#datetimepicker_date', "autocomplete": "off",
}), "data-target": "#datetimepicker_date",
}
),
} }
@ -108,10 +109,11 @@ class ChildDeleteForm(forms.ModelForm):
fields = [] fields = []
def clean_confirm_name(self): def clean_confirm_name(self):
confirm_name = self.cleaned_data['confirm_name'] confirm_name = self.cleaned_data["confirm_name"]
if confirm_name != str(self.instance): if confirm_name != str(self.instance):
raise forms.ValidationError( raise forms.ValidationError(
_('Name does not match child name.'), code='confirm_mismatch') _("Name does not match child name."), code="confirm_mismatch"
)
return confirm_name return confirm_name
def save(self, commit=True): def save(self, commit=True):
@ -123,88 +125,104 @@ class ChildDeleteForm(forms.ModelForm):
class DiaperChangeForm(CoreModelForm): class DiaperChangeForm(CoreModelForm):
class Meta: class Meta:
model = models.DiaperChange model = models.DiaperChange
fields = ['child', 'time', 'wet', 'solid', 'color', 'amount', 'notes'] fields = ["child", "time", "wet", "solid", "color", "amount", "notes"]
widgets = { widgets = {
'time': forms.DateTimeInput(attrs={ "time": forms.DateTimeInput(
'autocomplete': 'off', attrs={
'data-target': '#datetimepicker_time', "autocomplete": "off",
}), "data-target": "#datetimepicker_time",
'notes': forms.Textarea(attrs={'rows': 5}), }
),
"notes": forms.Textarea(attrs={"rows": 5}),
} }
class FeedingForm(CoreModelForm): class FeedingForm(CoreModelForm):
class Meta: class Meta:
model = models.Feeding model = models.Feeding
fields = ['child', 'start', 'end', 'type', 'method', 'amount', 'notes'] fields = ["child", "start", "end", "type", "method", "amount", "notes"]
widgets = { widgets = {
'start': forms.DateTimeInput(attrs={ "start": forms.DateTimeInput(
'autocomplete': 'off', attrs={
'data-target': '#datetimepicker_start', "autocomplete": "off",
}), "data-target": "#datetimepicker_start",
'end': forms.DateTimeInput(attrs={ }
'autocomplete': 'off', ),
'data-target': '#datetimepicker_end', "end": forms.DateTimeInput(
}), attrs={
'notes': forms.Textarea(attrs={'rows': 5}), "autocomplete": "off",
"data-target": "#datetimepicker_end",
}
),
"notes": forms.Textarea(attrs={"rows": 5}),
} }
class NoteForm(CoreModelForm): class NoteForm(CoreModelForm):
class Meta: class Meta:
model = models.Note model = models.Note
fields = ['child', 'note', 'time'] fields = ["child", "note", "time"]
widgets = { widgets = {
'time': forms.DateTimeInput(attrs={ "time": forms.DateTimeInput(
'autocomplete': 'off', attrs={
'data-target': '#datetimepicker_time', "autocomplete": "off",
}), "data-target": "#datetimepicker_time",
}
),
} }
class SleepForm(CoreModelForm): class SleepForm(CoreModelForm):
class Meta: class Meta:
model = models.Sleep model = models.Sleep
fields = ['child', 'start', 'end', 'notes'] fields = ["child", "start", "end", "notes"]
widgets = { widgets = {
'start': forms.DateTimeInput(attrs={ "start": forms.DateTimeInput(
'autocomplete': 'off', attrs={
'data-target': '#datetimepicker_start', "autocomplete": "off",
}), "data-target": "#datetimepicker_start",
'end': forms.DateTimeInput(attrs={ }
'autocomplete': 'off', ),
'data-target': '#datetimepicker_end', "end": forms.DateTimeInput(
}), attrs={
'notes': forms.Textarea(attrs={'rows': 5}), "autocomplete": "off",
"data-target": "#datetimepicker_end",
}
),
"notes": forms.Textarea(attrs={"rows": 5}),
} }
class TemperatureForm(CoreModelForm): class TemperatureForm(CoreModelForm):
class Meta: class Meta:
model = models.Temperature model = models.Temperature
fields = ['child', 'temperature', 'time', 'notes'] fields = ["child", "temperature", "time", "notes"]
widgets = { widgets = {
'time': forms.DateTimeInput(attrs={ "time": forms.DateTimeInput(
'autocomplete': 'off', attrs={
'data-target': '#datetimepicker_time', "autocomplete": "off",
}), "data-target": "#datetimepicker_time",
'notes': forms.Textarea(attrs={'rows': 5}), }
),
"notes": forms.Textarea(attrs={"rows": 5}),
} }
class TimerForm(CoreModelForm): class TimerForm(CoreModelForm):
class Meta: class Meta:
model = models.Timer model = models.Timer
fields = ['child', 'name', 'start'] fields = ["child", "name", "start"]
widgets = { widgets = {
'start': forms.DateTimeInput(attrs={ "start": forms.DateTimeInput(
'autocomplete': 'off', attrs={
'data-target': '#datetimepicker_start', "autocomplete": "off",
}) "data-target": "#datetimepicker_start",
}
)
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user') self.user = kwargs.pop("user")
super(TimerForm, self).__init__(*args, **kwargs) super(TimerForm, self).__init__(*args, **kwargs)
def save(self, commit=True): def save(self, commit=True):
@ -217,66 +235,78 @@ class TimerForm(CoreModelForm):
class TummyTimeForm(CoreModelForm): class TummyTimeForm(CoreModelForm):
class Meta: class Meta:
model = models.TummyTime model = models.TummyTime
fields = ['child', 'start', 'end', 'milestone'] fields = ["child", "start", "end", "milestone"]
widgets = { widgets = {
'start': forms.DateTimeInput(attrs={ "start": forms.DateTimeInput(
'autocomplete': 'off', attrs={
'data-target': '#datetimepicker_start', "autocomplete": "off",
}), "data-target": "#datetimepicker_start",
'end': forms.DateTimeInput(attrs={ }
'autocomplete': 'off', ),
'data-target': '#datetimepicker_end', "end": forms.DateTimeInput(
}), attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_end",
}
),
} }
class WeightForm(CoreModelForm): class WeightForm(CoreModelForm):
class Meta: class Meta:
model = models.Weight model = models.Weight
fields = ['child', 'weight', 'date', 'notes'] fields = ["child", "weight", "date", "notes"]
widgets = { widgets = {
'date': forms.DateInput(attrs={ "date": forms.DateInput(
'autocomplete': 'off', attrs={
'data-target': '#datetimepicker_date', "autocomplete": "off",
}), "data-target": "#datetimepicker_date",
'notes': forms.Textarea(attrs={'rows': 5}), }
),
"notes": forms.Textarea(attrs={"rows": 5}),
} }
class HeightForm(CoreModelForm): class HeightForm(CoreModelForm):
class Meta: class Meta:
model = models.Height model = models.Height
fields = ['child', 'height', 'date', 'notes'] fields = ["child", "height", "date", "notes"]
widgets = { widgets = {
'date': forms.DateInput(attrs={ "date": forms.DateInput(
'autocomplete': 'off', attrs={
'data-target': '#datetimepicker_date', "autocomplete": "off",
}), "data-target": "#datetimepicker_date",
'notes': forms.Textarea(attrs={'rows': 5}), }
),
"notes": forms.Textarea(attrs={"rows": 5}),
} }
class HeadCircumferenceForm(CoreModelForm): class HeadCircumferenceForm(CoreModelForm):
class Meta: class Meta:
model = models.HeadCircumference model = models.HeadCircumference
fields = ['child', 'head_circumference', 'date', 'notes'] fields = ["child", "head_circumference", "date", "notes"]
widgets = { widgets = {
'date': forms.DateInput(attrs={ "date": forms.DateInput(
'autocomplete': 'off', attrs={
'data-target': '#datetimepicker_date', "autocomplete": "off",
}), "data-target": "#datetimepicker_date",
'notes': forms.Textarea(attrs={'rows': 5}), }
),
"notes": forms.Textarea(attrs={"rows": 5}),
} }
class BMIForm(CoreModelForm): class BMIForm(CoreModelForm):
class Meta: class Meta:
model = models.BMI model = models.BMI
fields = ['child', 'bmi', 'date', 'notes'] fields = ["child", "bmi", "date", "notes"]
widgets = { widgets = {
'date': forms.DateInput(attrs={ "date": forms.DateInput(
'autocomplete': 'off', attrs={
'data-target': '#datetimepicker_date', "autocomplete": "off",
}), "data-target": "#datetimepicker_date",
'notes': forms.Textarea(attrs={'rows': 5}), }
),
"notes": forms.Textarea(attrs={"rows": 5}),
} }

View File

@ -15,109 +15,238 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Child', name="Child",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('first_name', models.CharField(max_length=255)), "id",
('last_name', models.CharField(max_length=255)), models.AutoField(
('birth_date', models.DateField()), auto_created=True,
('slug', models.SlugField(editable=False, max_length=100, unique=True)), primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("first_name", models.CharField(max_length=255)),
("last_name", models.CharField(max_length=255)),
("birth_date", models.DateField()),
("slug", models.SlugField(editable=False, max_length=100, unique=True)),
], ],
options={ options={
'ordering': ['last_name', 'first_name'], "ordering": ["last_name", "first_name"],
'default_permissions': ('view', 'add', 'change', 'delete'), "default_permissions": ("view", "add", "change", "delete"),
'verbose_name_plural': 'Children', "verbose_name_plural": "Children",
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='DiaperChange', name="DiaperChange",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('time', models.DateTimeField()), "id",
('wet', models.BooleanField()), models.AutoField(
('solid', models.BooleanField()), auto_created=True,
('color', models.CharField(blank=True, choices=[('black', 'Black'), ('brown', 'Brown'), ('green', 'Green'), ('yellow', 'Yellow')], max_length=255)), primary_key=True,
('child', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='diaper_change', to='core.Child')), serialize=False,
verbose_name="ID",
),
),
("time", models.DateTimeField()),
("wet", models.BooleanField()),
("solid", models.BooleanField()),
(
"color",
models.CharField(
blank=True,
choices=[
("black", "Black"),
("brown", "Brown"),
("green", "Green"),
("yellow", "Yellow"),
],
max_length=255,
),
),
(
"child",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="diaper_change",
to="core.Child",
),
),
], ],
options={ options={
'ordering': ['-time'], "ordering": ["-time"],
'default_permissions': ('view', 'add', 'change', 'delete'), "default_permissions": ("view", "add", "change", "delete"),
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Feeding', name="Feeding",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('start', models.DateTimeField()), "id",
('end', models.DateTimeField()), models.AutoField(
('duration', models.DurationField(editable=False, null=True)), auto_created=True,
('type', models.CharField(choices=[('breast milk', 'Breast milk'), ('formula', 'Formula')], max_length=255)), primary_key=True,
('method', models.CharField(choices=[('bottle', 'Bottle'), ('left breast', 'Left breast'), ('right breast', 'Right breast')], max_length=255)), serialize=False,
('amount', models.FloatField(blank=True, null=True)), verbose_name="ID",
('child', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feeding', to='core.Child')), ),
),
("start", models.DateTimeField()),
("end", models.DateTimeField()),
("duration", models.DurationField(editable=False, null=True)),
(
"type",
models.CharField(
choices=[
("breast milk", "Breast milk"),
("formula", "Formula"),
],
max_length=255,
),
),
(
"method",
models.CharField(
choices=[
("bottle", "Bottle"),
("left breast", "Left breast"),
("right breast", "Right breast"),
],
max_length=255,
),
),
("amount", models.FloatField(blank=True, null=True)),
(
"child",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="feeding",
to="core.Child",
),
),
], ],
options={ options={
'ordering': ['-start'], "ordering": ["-start"],
'default_permissions': ('view', 'add', 'change', 'delete'), "default_permissions": ("view", "add", "change", "delete"),
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Note', name="Note",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('note', models.TextField()), "id",
('time', models.DateTimeField(auto_now=True)), models.AutoField(
('child', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='note', to='core.Child')), auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("note", models.TextField()),
("time", models.DateTimeField(auto_now=True)),
(
"child",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="note",
to="core.Child",
),
),
], ],
options={ options={
'ordering': ['-time'], "ordering": ["-time"],
'default_permissions': ('view', 'add', 'change', 'delete'), "default_permissions": ("view", "add", "change", "delete"),
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Sleep', name="Sleep",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('start', models.DateTimeField()), "id",
('end', models.DateTimeField()), models.AutoField(
('duration', models.DurationField(editable=False, null=True)), auto_created=True,
('child', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sleep', to='core.Child')), primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("start", models.DateTimeField()),
("end", models.DateTimeField()),
("duration", models.DurationField(editable=False, null=True)),
(
"child",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="sleep",
to="core.Child",
),
),
], ],
options={ options={
'ordering': ['-start'], "ordering": ["-start"],
'default_permissions': ('view', 'add', 'change', 'delete'), "default_permissions": ("view", "add", "change", "delete"),
'verbose_name_plural': 'Sleep', "verbose_name_plural": "Sleep",
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Timer', name="Timer",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('name', models.CharField(blank=True, max_length=255, null=True)), "id",
('start', models.DateTimeField(auto_now_add=True)), models.AutoField(
('end', models.DateTimeField(blank=True, editable=False, null=True)), auto_created=True,
('duration', models.DurationField(editable=False, null=True)), primary_key=True,
('active', models.BooleanField(default=True, editable=False)), serialize=False,
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='timers', to=settings.AUTH_USER_MODEL)), verbose_name="ID",
),
),
("name", models.CharField(blank=True, max_length=255, null=True)),
("start", models.DateTimeField(auto_now_add=True)),
("end", models.DateTimeField(blank=True, editable=False, null=True)),
("duration", models.DurationField(editable=False, null=True)),
("active", models.BooleanField(default=True, editable=False)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="timers",
to=settings.AUTH_USER_MODEL,
),
),
], ],
options={ options={
'ordering': ['-active', '-start', '-end'], "ordering": ["-active", "-start", "-end"],
'default_permissions': ('view', 'add', 'change', 'delete'), "default_permissions": ("view", "add", "change", "delete"),
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='TummyTime', name="TummyTime",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('start', models.DateTimeField()), "id",
('end', models.DateTimeField()), models.AutoField(
('duration', models.DurationField(editable=False, null=True)), auto_created=True,
('milestone', models.CharField(blank=True, max_length=255)), primary_key=True,
('child', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tummy_time', to='core.Child')), serialize=False,
verbose_name="ID",
),
),
("start", models.DateTimeField()),
("end", models.DateTimeField()),
("duration", models.DurationField(editable=False, null=True)),
("milestone", models.CharField(blank=True, max_length=255)),
(
"child",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="tummy_time",
to="core.Child",
),
),
], ],
options={ options={
'ordering': ['-start'], "ordering": ["-start"],
'default_permissions': ('view', 'add', 'change', 'delete'), "default_permissions": ("view", "add", "change", "delete"),
}, },
), ),
] ]

View File

@ -7,13 +7,15 @@ import django.utils.timezone
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0001_initial'), ("core", "0001_initial"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='timer', model_name="timer",
name='start', name="start",
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Start Time'), field=models.DateTimeField(
default=django.utils.timezone.now, verbose_name="Start Time"
),
), ),
] ]

View File

@ -7,22 +7,37 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0002_auto_20171028_1257'), ("core", "0002_auto_20171028_1257"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Weight', name="Weight",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('weight', models.FloatField()), "id",
('date', models.DateField()), models.AutoField(
('child', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='weight', to='core.Child')), auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("weight", models.FloatField()),
("date", models.DateField()),
(
"child",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="weight",
to="core.Child",
),
),
], ],
options={ options={
'default_permissions': ('view', 'add', 'change', 'delete'), "default_permissions": ("view", "add", "change", "delete"),
'verbose_name_plural': 'Weight', "verbose_name_plural": "Weight",
'ordering': ['-date'], "ordering": ["-date"],
}, },
), ),
] ]

View File

@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0003_weight'), ("core", "0003_weight"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='child', model_name="child",
name='picture', name="picture",
field=models.ImageField(blank=True, null=True, upload_to='child/picture/'), field=models.ImageField(blank=True, null=True, upload_to="child/picture/"),
), ),
] ]

View File

@ -9,230 +9,352 @@ import django.utils.timezone
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0004_child_picture'), ("core", "0004_child_picture"),
] ]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='child', name="child",
options={'default_permissions': ('view', 'add', 'change', 'delete'), 'ordering': ['last_name', 'first_name'], 'verbose_name': 'Child', 'verbose_name_plural': 'Children'}, options={
"default_permissions": ("view", "add", "change", "delete"),
"ordering": ["last_name", "first_name"],
"verbose_name": "Child",
"verbose_name_plural": "Children",
},
), ),
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='diaperchange', name="diaperchange",
options={'default_permissions': ('view', 'add', 'change', 'delete'), 'ordering': ['-time'], 'verbose_name': 'Diaper Change', 'verbose_name_plural': 'Diaper Changes'}, options={
"default_permissions": ("view", "add", "change", "delete"),
"ordering": ["-time"],
"verbose_name": "Diaper Change",
"verbose_name_plural": "Diaper Changes",
},
), ),
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='feeding', name="feeding",
options={'default_permissions': ('view', 'add', 'change', 'delete'), 'ordering': ['-start'], 'verbose_name': 'Feeding', 'verbose_name_plural': 'Feedings'}, options={
"default_permissions": ("view", "add", "change", "delete"),
"ordering": ["-start"],
"verbose_name": "Feeding",
"verbose_name_plural": "Feedings",
},
), ),
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='note', name="note",
options={'default_permissions': ('view', 'add', 'change', 'delete'), 'ordering': ['-time'], 'verbose_name': 'Note', 'verbose_name_plural': 'Notes'}, options={
"default_permissions": ("view", "add", "change", "delete"),
"ordering": ["-time"],
"verbose_name": "Note",
"verbose_name_plural": "Notes",
},
), ),
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='sleep', name="sleep",
options={'default_permissions': ('view', 'add', 'change', 'delete'), 'ordering': ['-start'], 'verbose_name': 'Sleep', 'verbose_name_plural': 'Sleep'}, options={
"default_permissions": ("view", "add", "change", "delete"),
"ordering": ["-start"],
"verbose_name": "Sleep",
"verbose_name_plural": "Sleep",
},
), ),
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='timer', name="timer",
options={'default_permissions': ('view', 'add', 'change', 'delete'), 'ordering': ['-active', '-start', '-end'], 'verbose_name': 'Timer', 'verbose_name_plural': 'Timers'}, options={
"default_permissions": ("view", "add", "change", "delete"),
"ordering": ["-active", "-start", "-end"],
"verbose_name": "Timer",
"verbose_name_plural": "Timers",
},
), ),
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='tummytime', name="tummytime",
options={'default_permissions': ('view', 'add', 'change', 'delete'), 'ordering': ['-start'], 'verbose_name': 'Tummy Time', 'verbose_name_plural': 'Tummy Time'}, options={
"default_permissions": ("view", "add", "change", "delete"),
"ordering": ["-start"],
"verbose_name": "Tummy Time",
"verbose_name_plural": "Tummy Time",
},
), ),
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='weight', name="weight",
options={'default_permissions': ('view', 'add', 'change', 'delete'), 'ordering': ['-date'], 'verbose_name': 'Weight', 'verbose_name_plural': 'Weight'}, options={
"default_permissions": ("view", "add", "change", "delete"),
"ordering": ["-date"],
"verbose_name": "Weight",
"verbose_name_plural": "Weight",
},
), ),
migrations.AlterField( migrations.AlterField(
model_name='child', model_name="child",
name='birth_date', name="birth_date",
field=models.DateField(verbose_name='Birth date'), field=models.DateField(verbose_name="Birth date"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='child', model_name="child",
name='first_name', name="first_name",
field=models.CharField(max_length=255, verbose_name='First name'), field=models.CharField(max_length=255, verbose_name="First name"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='child', model_name="child",
name='last_name', name="last_name",
field=models.CharField(max_length=255, verbose_name='Last name'), field=models.CharField(max_length=255, verbose_name="Last name"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='child', model_name="child",
name='picture', name="picture",
field=models.ImageField(blank=True, null=True, upload_to='child/picture/', verbose_name='Picture'), field=models.ImageField(
blank=True,
null=True,
upload_to="child/picture/",
verbose_name="Picture",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='child', model_name="child",
name='slug', name="slug",
field=models.SlugField(editable=False, max_length=100, unique=True, verbose_name='Slug'), field=models.SlugField(
editable=False, max_length=100, unique=True, verbose_name="Slug"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='diaperchange', model_name="diaperchange",
name='child', name="child",
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='diaper_change', to='core.Child', verbose_name='Child'), field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="diaper_change",
to="core.Child",
verbose_name="Child",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='diaperchange', model_name="diaperchange",
name='color', name="color",
field=models.CharField(blank=True, choices=[('black', 'Black'), ('brown', 'Brown'), ('green', 'Green'), ('yellow', 'Yellow')], max_length=255, verbose_name='Color'), field=models.CharField(
blank=True,
choices=[
("black", "Black"),
("brown", "Brown"),
("green", "Green"),
("yellow", "Yellow"),
],
max_length=255,
verbose_name="Color",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='diaperchange', model_name="diaperchange",
name='solid', name="solid",
field=models.BooleanField(verbose_name='Solid'), field=models.BooleanField(verbose_name="Solid"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='diaperchange', model_name="diaperchange",
name='time', name="time",
field=models.DateTimeField(verbose_name='Time'), field=models.DateTimeField(verbose_name="Time"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='diaperchange', model_name="diaperchange",
name='wet', name="wet",
field=models.BooleanField(verbose_name='Wet'), field=models.BooleanField(verbose_name="Wet"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='feeding', model_name="feeding",
name='amount', name="amount",
field=models.FloatField(blank=True, null=True, verbose_name='Amount'), field=models.FloatField(blank=True, null=True, verbose_name="Amount"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='feeding', model_name="feeding",
name='child', name="child",
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feeding', to='core.Child', verbose_name='Child'), field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="feeding",
to="core.Child",
verbose_name="Child",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='feeding', model_name="feeding",
name='duration', name="duration",
field=models.DurationField(editable=False, null=True, verbose_name='Duration'), field=models.DurationField(
editable=False, null=True, verbose_name="Duration"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='feeding', model_name="feeding",
name='end', name="end",
field=models.DateTimeField(verbose_name='End time'), field=models.DateTimeField(verbose_name="End time"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='feeding', model_name="feeding",
name='method', name="method",
field=models.CharField(choices=[('bottle', 'Bottle'), ('left breast', 'Left breast'), ('right breast', 'Right breast')], max_length=255, verbose_name='Method'), field=models.CharField(
choices=[
("bottle", "Bottle"),
("left breast", "Left breast"),
("right breast", "Right breast"),
],
max_length=255,
verbose_name="Method",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='feeding', model_name="feeding",
name='start', name="start",
field=models.DateTimeField(verbose_name='Start time'), field=models.DateTimeField(verbose_name="Start time"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='feeding', model_name="feeding",
name='type', name="type",
field=models.CharField(choices=[('breast milk', 'Breast milk'), ('formula', 'Formula')], max_length=255, verbose_name='Type'), field=models.CharField(
choices=[("breast milk", "Breast milk"), ("formula", "Formula")],
max_length=255,
verbose_name="Type",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='note', model_name="note",
name='child', name="child",
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='note', to='core.Child', verbose_name='Child'), field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="note",
to="core.Child",
verbose_name="Child",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='note', model_name="note",
name='note', name="note",
field=models.TextField(verbose_name='Note'), field=models.TextField(verbose_name="Note"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='note', model_name="note",
name='time', name="time",
field=models.DateTimeField(auto_now=True, verbose_name='Time'), field=models.DateTimeField(auto_now=True, verbose_name="Time"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='sleep', model_name="sleep",
name='child', name="child",
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sleep', to='core.Child', verbose_name='Child'), field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="sleep",
to="core.Child",
verbose_name="Child",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='sleep', model_name="sleep",
name='duration', name="duration",
field=models.DurationField(editable=False, null=True, verbose_name='Duration'), field=models.DurationField(
editable=False, null=True, verbose_name="Duration"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='sleep', model_name="sleep",
name='end', name="end",
field=models.DateTimeField(verbose_name='End time'), field=models.DateTimeField(verbose_name="End time"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='sleep', model_name="sleep",
name='start', name="start",
field=models.DateTimeField(verbose_name='Start time'), field=models.DateTimeField(verbose_name="Start time"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='timer', model_name="timer",
name='active', name="active",
field=models.BooleanField(default=True, editable=False, verbose_name='Active'), field=models.BooleanField(
default=True, editable=False, verbose_name="Active"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='timer', model_name="timer",
name='duration', name="duration",
field=models.DurationField(editable=False, null=True, verbose_name='Duration'), field=models.DurationField(
editable=False, null=True, verbose_name="Duration"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='timer', model_name="timer",
name='end', name="end",
field=models.DateTimeField(blank=True, editable=False, null=True, verbose_name='End time'), field=models.DateTimeField(
blank=True, editable=False, null=True, verbose_name="End time"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='timer', model_name="timer",
name='name', name="name",
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Name'), field=models.CharField(
blank=True, max_length=255, null=True, verbose_name="Name"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='timer', model_name="timer",
name='start', name="start",
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Start time'), field=models.DateTimeField(
default=django.utils.timezone.now, verbose_name="Start time"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='timer', model_name="timer",
name='user', name="user",
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='timers', to=settings.AUTH_USER_MODEL, verbose_name='User'), field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="timers",
to=settings.AUTH_USER_MODEL,
verbose_name="User",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='tummytime', model_name="tummytime",
name='child', name="child",
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tummy_time', to='core.Child', verbose_name='Child'), field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="tummy_time",
to="core.Child",
verbose_name="Child",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='tummytime', model_name="tummytime",
name='duration', name="duration",
field=models.DurationField(editable=False, null=True, verbose_name='Duration'), field=models.DurationField(
editable=False, null=True, verbose_name="Duration"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='tummytime', model_name="tummytime",
name='end', name="end",
field=models.DateTimeField(verbose_name='End time'), field=models.DateTimeField(verbose_name="End time"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='tummytime', model_name="tummytime",
name='milestone', name="milestone",
field=models.CharField(blank=True, max_length=255, verbose_name='Milestone'), field=models.CharField(
blank=True, max_length=255, verbose_name="Milestone"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='tummytime', model_name="tummytime",
name='start', name="start",
field=models.DateTimeField(verbose_name='Start time'), field=models.DateTimeField(verbose_name="Start time"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='weight', model_name="weight",
name='child', name="child",
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='weight', to='core.Child', verbose_name='Child'), field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="weight",
to="core.Child",
verbose_name="Child",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='weight', model_name="weight",
name='date', name="date",
field=models.DateField(verbose_name='Date'), field=models.DateField(verbose_name="Date"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='weight', model_name="weight",
name='weight', name="weight",
field=models.FloatField(verbose_name='Weight'), field=models.FloatField(verbose_name="Weight"),
), ),
] ]

View File

@ -6,13 +6,22 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0005_auto_20190416_2048'), ("core", "0005_auto_20190416_2048"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='feeding', model_name="feeding",
name='method', name="method",
field=models.CharField(choices=[('bottle', 'Bottle'), ('left breast', 'Left breast'), ('right breast', 'Right breast'), ('both breasts', 'Both breasts')], max_length=255, verbose_name='Method'), field=models.CharField(
choices=[
("bottle", "Bottle"),
("left breast", "Left breast"),
("right breast", "Right breast"),
("both breasts", "Both breasts"),
],
max_length=255,
verbose_name="Method",
),
), ),
] ]

View File

@ -7,23 +7,39 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0006_auto_20190502_1701'), ("core", "0006_auto_20190502_1701"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Temperature', name="Temperature",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('temperature', models.FloatField(verbose_name='Temperature')), "id",
('time', models.DateTimeField(verbose_name='Time')), models.AutoField(
('child', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='temperature', to='core.Child', verbose_name='Child')), auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("temperature", models.FloatField(verbose_name="Temperature")),
("time", models.DateTimeField(verbose_name="Time")),
(
"child",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="temperature",
to="core.Child",
verbose_name="Child",
),
),
], ],
options={ options={
'verbose_name': 'Temperature', "verbose_name": "Temperature",
'verbose_name_plural': 'Temperature', "verbose_name_plural": "Temperature",
'ordering': ['-time'], "ordering": ["-time"],
'default_permissions': ('view', 'add', 'change', 'delete'), "default_permissions": ("view", "add", "change", "delete"),
}, },
), ),
] ]

View File

@ -6,13 +6,21 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0007_temperature'), ("core", "0007_temperature"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='feeding', model_name="feeding",
name='type', name="type",
field=models.CharField(choices=[('breast milk', 'Breast milk'), ('formula', 'Formula'), ('fortified breast milk', 'Fortified breast milk')], max_length=255, verbose_name='Type'), field=models.CharField(
choices=[
("breast milk", "Breast milk"),
("formula", "Formula"),
("fortified breast milk", "Fortified breast milk"),
],
max_length=255,
verbose_name="Type",
),
), ),
] ]

View File

@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0008_auto_20190607_1422'), ("core", "0008_auto_20190607_1422"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='diaperchange', model_name="diaperchange",
name='amount', name="amount",
field=models.FloatField(blank=True, null=True, verbose_name='Amount'), field=models.FloatField(blank=True, null=True, verbose_name="Amount"),
), ),
] ]

View File

@ -7,13 +7,20 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0009_diaperchange_amount'), ("core", "0009_diaperchange_amount"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='timer', model_name="timer",
name='child', name="child",
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='timers', to='core.Child', verbose_name='Child'), field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="timers",
to="core.Child",
verbose_name="Child",
),
), ),
] ]

View File

@ -6,33 +6,33 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0010_timer_child'), ("core", "0010_timer_child"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='diaperchange', model_name="diaperchange",
name='notes', name="notes",
field=models.TextField(blank=True, null=True, verbose_name='Notes'), field=models.TextField(blank=True, null=True, verbose_name="Notes"),
), ),
migrations.AddField( migrations.AddField(
model_name='feeding', model_name="feeding",
name='notes', name="notes",
field=models.TextField(blank=True, null=True, verbose_name='Notes'), field=models.TextField(blank=True, null=True, verbose_name="Notes"),
), ),
migrations.AddField( migrations.AddField(
model_name='sleep', model_name="sleep",
name='notes', name="notes",
field=models.TextField(blank=True, null=True, verbose_name='Notes'), field=models.TextField(blank=True, null=True, verbose_name="Notes"),
), ),
migrations.AddField( migrations.AddField(
model_name='temperature', model_name="temperature",
name='notes', name="notes",
field=models.TextField(blank=True, null=True, verbose_name='Notes'), field=models.TextField(blank=True, null=True, verbose_name="Notes"),
), ),
migrations.AddField( migrations.AddField(
model_name='weight', model_name="weight",
name='notes', name="notes",
field=models.TextField(blank=True, null=True, verbose_name='Notes'), field=models.TextField(blank=True, null=True, verbose_name="Notes"),
), ),
] ]

View File

@ -7,13 +7,15 @@ import django.utils.timezone
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0011_auto_20200214_1939'), ("core", "0011_auto_20200214_1939"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='note', model_name="note",
name='time', name="time",
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Time'), field=models.DateTimeField(
default=django.utils.timezone.now, verbose_name="Time"
),
), ),
] ]

View File

@ -6,18 +6,38 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0012_auto_20200813_0238'), ("core", "0012_auto_20200813_0238"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='feeding', model_name="feeding",
name='method', name="method",
field=models.CharField(choices=[('bottle', 'Bottle'), ('left breast', 'Left breast'), ('right breast', 'Right breast'), ('both breasts', 'Both breasts'), ('parent fed', 'Parent fed'), ('self fed', 'Self fed')], max_length=255, verbose_name='Method'), field=models.CharField(
choices=[
("bottle", "Bottle"),
("left breast", "Left breast"),
("right breast", "Right breast"),
("both breasts", "Both breasts"),
("parent fed", "Parent fed"),
("self fed", "Self fed"),
],
max_length=255,
verbose_name="Method",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='feeding', model_name="feeding",
name='type', name="type",
field=models.CharField(choices=[('breast milk', 'Breast milk'), ('formula', 'Formula'), ('fortified breast milk', 'Fortified breast milk'), ('solid food', 'Solid food')], max_length=255, verbose_name='Type'), field=models.CharField(
choices=[
("breast milk", "Breast milk"),
("formula", "Formula"),
("fortified breast milk", "Fortified breast milk"),
("solid food", "Solid food"),
],
max_length=255,
verbose_name="Type",
),
), ),
] ]

View File

@ -6,13 +6,19 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0013_auto_20210415_0528'), ("core", "0013_auto_20210415_0528"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='child', model_name="child",
name='slug', name="slug",
field=models.SlugField(allow_unicode=True, editable=False, max_length=100, unique=True, verbose_name='Slug'), field=models.SlugField(
allow_unicode=True,
editable=False,
max_length=100,
unique=True,
verbose_name="Slug",
),
), ),
] ]

View File

@ -4,25 +4,26 @@ from django.db import migrations, models
def set_napping(apps, schema_editor): def set_napping(apps, schema_editor):
# The model must be imported to ensure its overridden `save` method is run. # The model must be imported to ensure its overridden `save` method is run.
from core import models from core import models
for sleep in models.Sleep.objects.all(): for sleep in models.Sleep.objects.all():
sleep.save() sleep.save()
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0014_alter_child_slug'), ("core", "0014_alter_child_slug"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='sleep', model_name="sleep",
name='napping', name="napping",
field=models.BooleanField(null=True, verbose_name='Napping'), field=models.BooleanField(null=True, verbose_name="Napping"),
), ),
migrations.RunPython(set_napping, reverse_code=migrations.RunPython.noop), migrations.RunPython(set_napping, reverse_code=migrations.RunPython.noop),
migrations.AlterField( migrations.AlterField(
model_name='sleep', model_name="sleep",
name='napping', name="napping",
field=models.BooleanField(verbose_name='Napping'), field=models.BooleanField(verbose_name="Napping"),
), ),
] ]

View File

@ -6,13 +6,15 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0015_add_nap_field_for_sleep'), ("core", "0015_add_nap_field_for_sleep"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='sleep', model_name="sleep",
name='napping', name="napping",
field=models.BooleanField(editable=False, null=True, verbose_name='Napping'), field=models.BooleanField(
editable=False, null=True, verbose_name="Napping"
),
), ),
] ]

View File

@ -6,13 +6,15 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0016_alter_sleep_napping'), ("core", "0016_alter_sleep_napping"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='child', model_name="child",
name='last_name', name="last_name",
field=models.CharField(blank=True, max_length=255, verbose_name='Last name'), field=models.CharField(
blank=True, max_length=255, verbose_name="Last name"
),
), ),
] ]

View File

@ -7,56 +7,116 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0017_alter_child_last_name'), ("core", "0017_alter_child_last_name"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Height', name="Height",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('height', models.FloatField(verbose_name='Height')), "id",
('date', models.DateField(verbose_name='Date')), models.AutoField(
('notes', models.TextField(blank=True, null=True, verbose_name='Notes')), auto_created=True,
('child', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='height', to='core.child', verbose_name='Child')), primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("height", models.FloatField(verbose_name="Height")),
("date", models.DateField(verbose_name="Date")),
(
"notes",
models.TextField(blank=True, null=True, verbose_name="Notes"),
),
(
"child",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="height",
to="core.child",
verbose_name="Child",
),
),
], ],
options={ options={
'verbose_name': 'Height', "verbose_name": "Height",
'verbose_name_plural': 'Height', "verbose_name_plural": "Height",
'ordering': ['-date'], "ordering": ["-date"],
'default_permissions': ('view', 'add', 'change', 'delete'), "default_permissions": ("view", "add", "change", "delete"),
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='HeadCircumference', name="HeadCircumference",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('head_circumference', models.FloatField(verbose_name='Head Circumference')), "id",
('date', models.DateField(verbose_name='Date')), models.AutoField(
('notes', models.TextField(blank=True, null=True, verbose_name='Notes')), auto_created=True,
('child', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='head_circumference', to='core.child', verbose_name='Child')), primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"head_circumference",
models.FloatField(verbose_name="Head Circumference"),
),
("date", models.DateField(verbose_name="Date")),
(
"notes",
models.TextField(blank=True, null=True, verbose_name="Notes"),
),
(
"child",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="head_circumference",
to="core.child",
verbose_name="Child",
),
),
], ],
options={ options={
'verbose_name': 'Head Circumference', "verbose_name": "Head Circumference",
'verbose_name_plural': 'Head Circumference', "verbose_name_plural": "Head Circumference",
'ordering': ['-date'], "ordering": ["-date"],
'default_permissions': ('view', 'add', 'change', 'delete'), "default_permissions": ("view", "add", "change", "delete"),
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='BMI', name="BMI",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('bmi', models.FloatField(verbose_name='BMI')), "id",
('date', models.DateField(verbose_name='Date')), models.AutoField(
('notes', models.TextField(blank=True, null=True, verbose_name='Notes')), auto_created=True,
('child', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bmi', to='core.child', verbose_name='Child')), primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("bmi", models.FloatField(verbose_name="BMI")),
("date", models.DateField(verbose_name="Date")),
(
"notes",
models.TextField(blank=True, null=True, verbose_name="Notes"),
),
(
"child",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="bmi",
to="core.child",
verbose_name="Child",
),
),
], ],
options={ options={
'verbose_name': 'BMI', "verbose_name": "BMI",
'verbose_name_plural': 'BMI', "verbose_name_plural": "BMI",
'ordering': ['-date'], "ordering": ["-date"],
'default_permissions': ('view', 'add', 'change', 'delete'), "default_permissions": ("view", "add", "change", "delete"),
}, },
), ),
] ]

View File

@ -20,8 +20,8 @@ def validate_date(date, field_name):
""" """
if date and date > timezone.localdate(): if date and date > timezone.localdate():
raise ValidationError( raise ValidationError(
{field_name: _('Date can not be in the future.')}, {field_name: _("Date can not be in the future.")}, code="date_invalid"
code='date_invalid') )
def validate_duration(model, max_duration=timedelta(hours=24)): def validate_duration(model, max_duration=timedelta(hours=24)):
@ -34,10 +34,10 @@ def validate_duration(model, max_duration=timedelta(hours=24)):
if model.start and model.end: if model.start and model.end:
if model.start > model.end: if model.start > model.end:
raise ValidationError( raise ValidationError(
_('Start time must come before end time.'), _("Start time must come before end time."), code="end_before_start"
code='end_before_start') )
if model.end - model.start > max_duration: if model.end - model.start > max_duration:
raise ValidationError(_('Duration too long.'), code='max_duration') raise ValidationError(_("Duration too long."), code="max_duration")
def validate_unique_period(queryset, model): def validate_unique_period(queryset, model):
@ -53,8 +53,9 @@ def validate_unique_period(queryset, model):
if model.start and model.end: if model.start and model.end:
if queryset.filter(start__lt=model.end, end__gt=model.start): if queryset.filter(start__lt=model.end, end__gt=model.start):
raise ValidationError( raise ValidationError(
_('Another entry intersects the specified time period.'), _("Another entry intersects the specified time period."),
code='period_intersection') code="period_intersection",
)
def validate_time(time, field_name): def validate_time(time, field_name):
@ -66,47 +67,38 @@ def validate_time(time, field_name):
""" """
if time and time > timezone.localtime(): if time and time > timezone.localtime():
raise ValidationError( raise ValidationError(
{field_name: _('Date/time can not be in the future.')}, {field_name: _("Date/time can not be in the future.")}, code="time_invalid"
code='time_invalid') )
class Child(models.Model): class Child(models.Model):
model_name = 'child' model_name = "child"
first_name = models.CharField(max_length=255, verbose_name=_('First name')) first_name = models.CharField(max_length=255, verbose_name=_("First name"))
last_name = models.CharField( last_name = models.CharField(
blank=True, blank=True, max_length=255, verbose_name=_("Last name")
max_length=255,
verbose_name=_('Last name')
)
birth_date = models.DateField(
blank=False,
null=False,
verbose_name=_('Birth date')
) )
birth_date = models.DateField(blank=False, null=False, verbose_name=_("Birth date"))
slug = models.SlugField( slug = models.SlugField(
allow_unicode=True, allow_unicode=True,
blank=False, blank=False,
editable=False, editable=False,
max_length=100, max_length=100,
unique=True, unique=True,
verbose_name=_('Slug') verbose_name=_("Slug"),
) )
picture = models.ImageField( picture = models.ImageField(
blank=True, blank=True, null=True, upload_to="child/picture/", verbose_name=_("Picture")
null=True,
upload_to='child/picture/',
verbose_name=_('Picture')
) )
objects = models.Manager() objects = models.Manager()
cache_key_count = 'core.child.count' cache_key_count = "core.child.count"
class Meta: class Meta:
default_permissions = ('view', 'add', 'change', 'delete') default_permissions = ("view", "add", "change", "delete")
ordering = ['last_name', 'first_name'] ordering = ["last_name", "first_name"]
verbose_name = _('Child') verbose_name = _("Child")
verbose_name_plural = _('Children') verbose_name_plural = _("Children")
def __str__(self): def __str__(self):
return self.name() return self.name()
@ -124,8 +116,8 @@ class Child(models.Model):
if not self.last_name: if not self.last_name:
return self.first_name return self.first_name
if reverse: if reverse:
return '{}, {}'.format(self.last_name, self.first_name) return "{}, {}".format(self.last_name, self.first_name)
return '{} {}'.format(self.first_name, self.last_name) return "{} {}".format(self.first_name, self.last_name)
@classmethod @classmethod
def count(cls): def count(cls):
@ -134,122 +126,109 @@ class Child(models.Model):
class DiaperChange(models.Model): class DiaperChange(models.Model):
model_name = 'diaperchange' model_name = "diaperchange"
child = models.ForeignKey( child = models.ForeignKey(
'Child', "Child",
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='diaper_change', related_name="diaper_change",
verbose_name=_('Child') verbose_name=_("Child"),
) )
time = models.DateTimeField( time = models.DateTimeField(blank=False, null=False, verbose_name=_("Time"))
blank=False, wet = models.BooleanField(verbose_name=_("Wet"))
null=False, solid = models.BooleanField(verbose_name=_("Solid"))
verbose_name=_('Time')
)
wet = models.BooleanField(verbose_name=_('Wet'))
solid = models.BooleanField(verbose_name=_('Solid'))
color = models.CharField( color = models.CharField(
blank=True, blank=True,
choices=[ choices=[
('black', _('Black')), ("black", _("Black")),
('brown', _('Brown')), ("brown", _("Brown")),
('green', _('Green')), ("green", _("Green")),
('yellow', _('Yellow')), ("yellow", _("Yellow")),
], ],
max_length=255, max_length=255,
verbose_name=_('Color') verbose_name=_("Color"),
) )
amount = models.FloatField(blank=True, null=True, verbose_name=_('Amount')) amount = models.FloatField(blank=True, null=True, verbose_name=_("Amount"))
notes = models.TextField(blank=True, null=True, verbose_name=_('Notes')) notes = models.TextField(blank=True, null=True, verbose_name=_("Notes"))
objects = models.Manager() objects = models.Manager()
class Meta: class Meta:
default_permissions = ('view', 'add', 'change', 'delete') default_permissions = ("view", "add", "change", "delete")
ordering = ['-time'] ordering = ["-time"]
verbose_name = _('Diaper Change') verbose_name = _("Diaper Change")
verbose_name_plural = _('Diaper Changes') verbose_name_plural = _("Diaper Changes")
def __str__(self): def __str__(self):
return str(_('Diaper Change')) return str(_("Diaper Change"))
def attributes(self): def attributes(self):
attributes = [] attributes = []
if self.wet: if self.wet:
attributes.append(self._meta.get_field('wet').verbose_name) attributes.append(self._meta.get_field("wet").verbose_name)
if self.solid: if self.solid:
attributes.append(self._meta.get_field('solid').verbose_name) attributes.append(self._meta.get_field("solid").verbose_name)
if self.color: if self.color:
attributes.append(self.get_color_display()) attributes.append(self.get_color_display())
return attributes return attributes
def clean(self): def clean(self):
validate_time(self.time, 'time') validate_time(self.time, "time")
# One or both of Wet and Solid is required. # One or both of Wet and Solid is required.
if not self.wet and not self.solid: if not self.wet and not self.solid:
raise ValidationError( raise ValidationError(
_('Wet and/or solid is required.'), code='wet_or_solid') _("Wet and/or solid is required."), code="wet_or_solid"
)
class Feeding(models.Model): class Feeding(models.Model):
model_name = 'feeding' model_name = "feeding"
child = models.ForeignKey( child = models.ForeignKey(
'Child', "Child",
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='feeding', related_name="feeding",
verbose_name=_('Child') verbose_name=_("Child"),
)
start = models.DateTimeField(
blank=False,
null=False,
verbose_name=_('Start time')
)
end = models.DateTimeField(
blank=False,
null=False,
verbose_name=_('End time')
) )
start = models.DateTimeField(blank=False, null=False, verbose_name=_("Start time"))
end = models.DateTimeField(blank=False, null=False, verbose_name=_("End time"))
duration = models.DurationField( duration = models.DurationField(
editable=False, editable=False, null=True, verbose_name=_("Duration")
null=True,
verbose_name=_('Duration')
) )
type = models.CharField( type = models.CharField(
choices=[ choices=[
('breast milk', _('Breast milk')), ("breast milk", _("Breast milk")),
('formula', _('Formula')), ("formula", _("Formula")),
('fortified breast milk', _('Fortified breast milk')), ("fortified breast milk", _("Fortified breast milk")),
('solid food', _('Solid food')), ("solid food", _("Solid food")),
], ],
max_length=255, max_length=255,
verbose_name=_('Type') verbose_name=_("Type"),
) )
method = models.CharField( method = models.CharField(
choices=[ choices=[
('bottle', _('Bottle')), ("bottle", _("Bottle")),
('left breast', _('Left breast')), ("left breast", _("Left breast")),
('right breast', _('Right breast')), ("right breast", _("Right breast")),
('both breasts', _('Both breasts')), ("both breasts", _("Both breasts")),
('parent fed', _('Parent fed')), ("parent fed", _("Parent fed")),
('self fed', _('Self fed')), ("self fed", _("Self fed")),
], ],
max_length=255, max_length=255,
verbose_name=_('Method') verbose_name=_("Method"),
) )
amount = models.FloatField(blank=True, null=True, verbose_name=_('Amount')) amount = models.FloatField(blank=True, null=True, verbose_name=_("Amount"))
notes = models.TextField(blank=True, null=True, verbose_name=_('Notes')) notes = models.TextField(blank=True, null=True, verbose_name=_("Notes"))
objects = models.Manager() objects = models.Manager()
class Meta: class Meta:
default_permissions = ('view', 'add', 'change', 'delete') default_permissions = ("view", "add", "change", "delete")
ordering = ['-start'] ordering = ["-start"]
verbose_name = _('Feeding') verbose_name = _("Feeding")
verbose_name_plural = _('Feedings') verbose_name_plural = _("Feedings")
def __str__(self): def __str__(self):
return str(_('Feeding')) return str(_("Feeding"))
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.start and self.end: if self.start and self.end:
@ -257,37 +236,32 @@ class Feeding(models.Model):
super(Feeding, self).save(*args, **kwargs) super(Feeding, self).save(*args, **kwargs)
def clean(self): def clean(self):
validate_time(self.start, 'start') validate_time(self.start, "start")
validate_time(self.end, 'end') validate_time(self.end, "end")
validate_duration(self) validate_duration(self)
validate_unique_period(Feeding.objects.filter(child=self.child), self) validate_unique_period(Feeding.objects.filter(child=self.child), self)
class Note(models.Model): class Note(models.Model):
model_name = 'note' model_name = "note"
child = models.ForeignKey( child = models.ForeignKey(
'Child', "Child", on_delete=models.CASCADE, related_name="note", verbose_name=_("Child")
on_delete=models.CASCADE,
related_name='note',
verbose_name=_('Child')
) )
note = models.TextField(verbose_name=_('Note')) note = models.TextField(verbose_name=_("Note"))
time = models.DateTimeField( time = models.DateTimeField(
default=timezone.now, default=timezone.now, blank=False, verbose_name=_("Time")
blank=False,
verbose_name=_('Time')
) )
objects = models.Manager() objects = models.Manager()
class Meta: class Meta:
default_permissions = ('view', 'add', 'change', 'delete') default_permissions = ("view", "add", "change", "delete")
ordering = ['-time'] ordering = ["-time"]
verbose_name = _('Note') verbose_name = _("Note")
verbose_name_plural = _('Notes') verbose_name_plural = _("Notes")
def __str__(self): def __str__(self):
return str(_('Note')) return str(_("Note"))
class NapsManager(models.Manager): class NapsManager(models.Manager):
@ -297,53 +271,38 @@ class NapsManager(models.Manager):
class Sleep(models.Model): class Sleep(models.Model):
model_name = 'sleep' model_name = "sleep"
child = models.ForeignKey( child = models.ForeignKey(
'Child', "Child", on_delete=models.CASCADE, related_name="sleep", verbose_name=_("Child")
on_delete=models.CASCADE,
related_name='sleep',
verbose_name=_('Child')
)
napping = models.BooleanField(
editable=False,
null=True,
verbose_name=_('Napping')
)
start = models.DateTimeField(
blank=False,
null=False,
verbose_name=_('Start time')
)
end = models.DateTimeField(
blank=False,
null=False,
verbose_name=_('End time')
) )
napping = models.BooleanField(editable=False, null=True, verbose_name=_("Napping"))
start = models.DateTimeField(blank=False, null=False, verbose_name=_("Start time"))
end = models.DateTimeField(blank=False, null=False, verbose_name=_("End time"))
duration = models.DurationField( duration = models.DurationField(
editable=False, editable=False, null=True, verbose_name=_("Duration")
null=True,
verbose_name=_('Duration')
) )
notes = models.TextField(blank=True, null=True, verbose_name=_('Notes')) notes = models.TextField(blank=True, null=True, verbose_name=_("Notes"))
objects = models.Manager() objects = models.Manager()
naps = NapsManager() naps = NapsManager()
class Meta: class Meta:
default_permissions = ('view', 'add', 'change', 'delete') default_permissions = ("view", "add", "change", "delete")
ordering = ['-start'] ordering = ["-start"]
verbose_name = _('Sleep') verbose_name = _("Sleep")
verbose_name_plural = _('Sleep') verbose_name_plural = _("Sleep")
def __str__(self): def __str__(self):
return str(_('Sleep')) return str(_("Sleep"))
@property @property
def nap(self): def nap(self):
nap_start_min = timezone.datetime.strptime( nap_start_min = timezone.datetime.strptime(
settings.BABY_BUDDY['NAP_START_MIN'], '%H:%M').time() settings.BABY_BUDDY["NAP_START_MIN"], "%H:%M"
).time()
nap_start_max = timezone.datetime.strptime( nap_start_max = timezone.datetime.strptime(
settings.BABY_BUDDY['NAP_START_MAX'], '%H:%M').time() settings.BABY_BUDDY["NAP_START_MAX"], "%H:%M"
).time()
local_start_time = timezone.localtime(self.start).time() local_start_time = timezone.localtime(self.start).time()
return nap_start_min <= local_start_time <= nap_start_max return nap_start_min <= local_start_time <= nap_start_max
@ -354,101 +313,81 @@ class Sleep(models.Model):
super(Sleep, self).save(*args, **kwargs) super(Sleep, self).save(*args, **kwargs)
def clean(self): def clean(self):
validate_time(self.start, 'start') validate_time(self.start, "start")
validate_time(self.end, 'end') validate_time(self.end, "end")
validate_duration(self) validate_duration(self)
validate_unique_period(Sleep.objects.filter(child=self.child), self) validate_unique_period(Sleep.objects.filter(child=self.child), self)
class Temperature(models.Model): class Temperature(models.Model):
model_name = 'temperature' model_name = "temperature"
child = models.ForeignKey( child = models.ForeignKey(
'Child', "Child",
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='temperature', related_name="temperature",
verbose_name=_('Child') verbose_name=_("Child"),
) )
temperature = models.FloatField( temperature = models.FloatField(
blank=False, blank=False, null=False, verbose_name=_("Temperature")
null=False,
verbose_name=_('Temperature')
) )
time = models.DateTimeField( time = models.DateTimeField(blank=False, null=False, verbose_name=_("Time"))
blank=False, notes = models.TextField(blank=True, null=True, verbose_name=_("Notes"))
null=False,
verbose_name=_('Time')
)
notes = models.TextField(blank=True, null=True, verbose_name=_('Notes'))
objects = models.Manager() objects = models.Manager()
class Meta: class Meta:
default_permissions = ('view', 'add', 'change', 'delete') default_permissions = ("view", "add", "change", "delete")
ordering = ['-time'] ordering = ["-time"]
verbose_name = _('Temperature') verbose_name = _("Temperature")
verbose_name_plural = _('Temperature') verbose_name_plural = _("Temperature")
def __str__(self): def __str__(self):
return str(_('Temperature')) return str(_("Temperature"))
def clean(self): def clean(self):
validate_time(self.time, 'time') validate_time(self.time, "time")
class Timer(models.Model): class Timer(models.Model):
model_name = 'timer' model_name = "timer"
child = models.ForeignKey( child = models.ForeignKey(
'Child', "Child",
blank=True, blank=True,
null=True, null=True,
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='timers', related_name="timers",
verbose_name=_('Child') verbose_name=_("Child"),
) )
name = models.CharField( name = models.CharField(
blank=True, blank=True, max_length=255, null=True, verbose_name=_("Name")
max_length=255,
null=True,
verbose_name=_('Name')
) )
start = models.DateTimeField( start = models.DateTimeField(
default=timezone.now, default=timezone.now, blank=False, verbose_name=_("Start time")
blank=False,
verbose_name=_('Start time')
) )
end = models.DateTimeField( end = models.DateTimeField(
blank=True, blank=True, editable=False, null=True, verbose_name=_("End time")
editable=False,
null=True,
verbose_name=_('End time')
) )
duration = models.DurationField( duration = models.DurationField(
editable=False, editable=False, null=True, verbose_name=_("Duration")
null=True,
verbose_name=_('Duration')
)
active = models.BooleanField(
default=True,
editable=False,
verbose_name=_('Active')
) )
active = models.BooleanField(default=True, editable=False, verbose_name=_("Active"))
user = models.ForeignKey( user = models.ForeignKey(
'auth.User', "auth.User",
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='timers', related_name="timers",
verbose_name=_('User') verbose_name=_("User"),
) )
objects = models.Manager() objects = models.Manager()
class Meta: class Meta:
default_permissions = ('view', 'add', 'change', 'delete') default_permissions = ("view", "add", "change", "delete")
ordering = ['-active', '-start', '-end'] ordering = ["-active", "-start", "-end"]
verbose_name = _('Timer') verbose_name = _("Timer")
verbose_name_plural = _('Timers') verbose_name_plural = _("Timers")
def __str__(self): def __str__(self):
return self.name or str(format_lazy(_('Timer #{id}'), id=self.id)) return self.name or str(format_lazy(_("Timer #{id}"), id=self.id))
@property @property
def title_with_child(self): def title_with_child(self):
@ -456,8 +395,7 @@ class Timer(models.Model):
title = str(self) title = str(self)
# Only actually add the name if there is more than one Child instance. # Only actually add the name if there is more than one Child instance.
if title and self.child and Child.count() > 1: if title and self.child and Child.count() > 1:
title = format_lazy('{title} ({child})', title=title, title = format_lazy("{title} ({child})", title=title, child=self.child)
child=self.child)
return title return title
@property @property
@ -499,51 +437,39 @@ class Timer(models.Model):
super(Timer, self).save(*args, **kwargs) super(Timer, self).save(*args, **kwargs)
def clean(self): def clean(self):
validate_time(self.start, 'start') validate_time(self.start, "start")
if self.end: if self.end:
validate_time(self.end, 'end') validate_time(self.end, "end")
validate_duration(self) validate_duration(self)
class TummyTime(models.Model): class TummyTime(models.Model):
model_name = 'tummytime' model_name = "tummytime"
child = models.ForeignKey( child = models.ForeignKey(
'Child', "Child",
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='tummy_time', related_name="tummy_time",
verbose_name=_('Child') verbose_name=_("Child"),
)
start = models.DateTimeField(
blank=False,
null=False,
verbose_name=_('Start time')
)
end = models.DateTimeField(
blank=False,
null=False,
verbose_name=_('End time')
) )
start = models.DateTimeField(blank=False, null=False, verbose_name=_("Start time"))
end = models.DateTimeField(blank=False, null=False, verbose_name=_("End time"))
duration = models.DurationField( duration = models.DurationField(
editable=False, editable=False, null=True, verbose_name=_("Duration")
null=True,
verbose_name=_('Duration')
) )
milestone = models.CharField( milestone = models.CharField(
blank=True, blank=True, max_length=255, verbose_name=_("Milestone")
max_length=255,
verbose_name=_('Milestone')
) )
objects = models.Manager() objects = models.Manager()
class Meta: class Meta:
default_permissions = ('view', 'add', 'change', 'delete') default_permissions = ("view", "add", "change", "delete")
ordering = ['-start'] ordering = ["-start"]
verbose_name = _('Tummy Time') verbose_name = _("Tummy Time")
verbose_name_plural = _('Tummy Time') verbose_name_plural = _("Tummy Time")
def __str__(self): def __str__(self):
return str(_('Tummy Time')) return str(_("Tummy Time"))
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.start and self.end: if self.start and self.end:
@ -551,148 +477,114 @@ class TummyTime(models.Model):
super(TummyTime, self).save(*args, **kwargs) super(TummyTime, self).save(*args, **kwargs)
def clean(self): def clean(self):
validate_time(self.start, 'start') validate_time(self.start, "start")
validate_time(self.end, 'end') validate_time(self.end, "end")
validate_duration(self) validate_duration(self)
validate_unique_period( validate_unique_period(TummyTime.objects.filter(child=self.child), self)
TummyTime.objects.filter(child=self.child), self)
class Weight(models.Model): class Weight(models.Model):
model_name = 'weight' model_name = "weight"
child = models.ForeignKey( child = models.ForeignKey(
'Child', "Child",
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='weight', related_name="weight",
verbose_name=_('Child') verbose_name=_("Child"),
) )
weight = models.FloatField( weight = models.FloatField(blank=False, null=False, verbose_name=_("Weight"))
blank=False, date = models.DateField(blank=False, null=False, verbose_name=_("Date"))
null=False, notes = models.TextField(blank=True, null=True, verbose_name=_("Notes"))
verbose_name=_('Weight')
)
date = models.DateField(
blank=False,
null=False,
verbose_name=_('Date')
)
notes = models.TextField(blank=True, null=True, verbose_name=_('Notes'))
objects = models.Manager() objects = models.Manager()
class Meta: class Meta:
default_permissions = ('view', 'add', 'change', 'delete') default_permissions = ("view", "add", "change", "delete")
ordering = ['-date'] ordering = ["-date"]
verbose_name = _('Weight') verbose_name = _("Weight")
verbose_name_plural = _('Weight') verbose_name_plural = _("Weight")
def __str__(self): def __str__(self):
return str(_('Weight')) return str(_("Weight"))
def clean(self): def clean(self):
validate_date(self.date, 'date') validate_date(self.date, "date")
class Height(models.Model): class Height(models.Model):
model_name = 'height' model_name = "height"
child = models.ForeignKey( child = models.ForeignKey(
'Child', "Child",
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='height', related_name="height",
verbose_name=_('Child') verbose_name=_("Child"),
) )
height = models.FloatField( height = models.FloatField(blank=False, null=False, verbose_name=_("Height"))
blank=False, date = models.DateField(blank=False, null=False, verbose_name=_("Date"))
null=False, notes = models.TextField(blank=True, null=True, verbose_name=_("Notes"))
verbose_name=_('Height')
)
date = models.DateField(
blank=False,
null=False,
verbose_name=_('Date')
)
notes = models.TextField(blank=True, null=True, verbose_name=_('Notes'))
objects = models.Manager() objects = models.Manager()
class Meta: class Meta:
default_permissions = ('view', 'add', 'change', 'delete') default_permissions = ("view", "add", "change", "delete")
ordering = ['-date'] ordering = ["-date"]
verbose_name = _('Height') verbose_name = _("Height")
verbose_name_plural = _('Height') verbose_name_plural = _("Height")
def __str__(self): def __str__(self):
return str(_('Height')) return str(_("Height"))
def clean(self): def clean(self):
validate_date(self.date, 'date') validate_date(self.date, "date")
class HeadCircumference(models.Model): class HeadCircumference(models.Model):
model_name = 'head_circumference' model_name = "head_circumference"
child = models.ForeignKey( child = models.ForeignKey(
'Child', "Child",
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='head_circumference', related_name="head_circumference",
verbose_name=_('Child') verbose_name=_("Child"),
) )
head_circumference = models.FloatField( head_circumference = models.FloatField(
blank=False, blank=False, null=False, verbose_name=_("Head Circumference")
null=False,
verbose_name=_('Head Circumference')
) )
date = models.DateField( date = models.DateField(blank=False, null=False, verbose_name=_("Date"))
blank=False, notes = models.TextField(blank=True, null=True, verbose_name=_("Notes"))
null=False,
verbose_name=_('Date')
)
notes = models.TextField(blank=True, null=True, verbose_name=_('Notes'))
objects = models.Manager() objects = models.Manager()
class Meta: class Meta:
default_permissions = ('view', 'add', 'change', 'delete') default_permissions = ("view", "add", "change", "delete")
ordering = ['-date'] ordering = ["-date"]
verbose_name = _('Head Circumference') verbose_name = _("Head Circumference")
verbose_name_plural = _('Head Circumference') verbose_name_plural = _("Head Circumference")
def __str__(self): def __str__(self):
return str(_('Head Circumference')) return str(_("Head Circumference"))
def clean(self): def clean(self):
validate_date(self.date, 'date') validate_date(self.date, "date")
class BMI(models.Model): class BMI(models.Model):
model_name = 'bmi' model_name = "bmi"
child = models.ForeignKey( child = models.ForeignKey(
'Child', "Child", on_delete=models.CASCADE, related_name="bmi", verbose_name=_("Child")
on_delete=models.CASCADE,
related_name='bmi',
verbose_name=_('Child')
) )
bmi = models.FloatField( bmi = models.FloatField(blank=False, null=False, verbose_name=_("BMI"))
blank=False, date = models.DateField(blank=False, null=False, verbose_name=_("Date"))
null=False, notes = models.TextField(blank=True, null=True, verbose_name=_("Notes"))
verbose_name=_('BMI')
)
date = models.DateField(
blank=False,
null=False,
verbose_name=_('Date')
)
notes = models.TextField(blank=True, null=True, verbose_name=_('Notes'))
objects = models.Manager() objects = models.Manager()
class Meta: class Meta:
default_permissions = ('view', 'add', 'change', 'delete') default_permissions = ("view", "add", "change", "delete")
ordering = ['-date'] ordering = ["-date"]
verbose_name = _('BMI') verbose_name = _("BMI")
verbose_name_plural = _('BMI') verbose_name_plural = _("BMI")
def __str__(self): def __str__(self):
return str(_('BMI')) return str(_("BMI"))
def clean(self): def clean(self):
validate_date(self.date, 'date') validate_date(self.date, "date")

View File

@ -13,8 +13,8 @@ def bool_icon(value):
:returns: a string of html for an icon representing the boolean. :returns: a string of html for an icon representing the boolean.
""" """
if value: if value:
classes = 'icon-true text-success' classes = "icon-true text-success"
else: else:
classes = 'icon-false text-danger' classes = "icon-false text-danger"
icon_html = '<i class="{}" aria-hidden="true"></i>'.format(classes) icon_html = '<i class="{}" aria-hidden="true"></i>'.format(classes)
return mark_safe(icon_html) return mark_safe(icon_html)

View File

@ -8,7 +8,7 @@ register = template.Library()
@register.simple_tag(takes_context=True) @register.simple_tag(takes_context=True)
def datetimepicker_format(context, format_string='L LT'): def datetimepicker_format(context, format_string="L LT"):
""" """
Return a datetime format string for momentjs, with support for 24 hour time Return a datetime format string for momentjs, with support for 24 hour time
override setting. override setting.
@ -17,8 +17,8 @@ def datetimepicker_format(context, format_string='L LT'):
:return: the format string to use, as 24 hour time if configured. :return: the format string to use, as 24 hour time if configured.
""" """
try: try:
user = context['request'].user user = context["request"].user
if hasattr(user, 'settings') and user.settings.language: if hasattr(user, "settings") and user.settings.language:
language = user.settings.language language = user.settings.language
else: else:
language = settings.LANGUAGE_CODE language = settings.LANGUAGE_CODE
@ -26,17 +26,17 @@ def datetimepicker_format(context, format_string='L LT'):
language = None language = None
if settings.USE_24_HOUR_TIME_FORMAT: if settings.USE_24_HOUR_TIME_FORMAT:
if format_string == 'L LT': if format_string == "L LT":
format_string = 'L HH:mm' format_string = "L HH:mm"
elif format_string == 'L LTS': elif format_string == "L LTS":
format_string = 'L HH:mm:ss' format_string = "L HH:mm:ss"
elif language and language == 'en-GB': elif language and language == "en-GB":
# Force 12-hour format if 24 hour format is not configured for en-GB # Force 12-hour format if 24 hour format is not configured for en-GB
# (Django default is 12H, momentjs default is 24H). # (Django default is 12H, momentjs default is 24H).
if format_string == 'L LT': if format_string == "L LT":
format_string = 'L h:mm a' format_string = "L h:mm a"
elif format_string == 'L LTS': elif format_string == "L LTS":
format_string = 'L h:mm:ss a' format_string = "L h:mm:ss a"
return format_string return format_string
@ -57,21 +57,22 @@ def datetime_short(date):
now = timezone.localtime() now = timezone.localtime()
if now.date() == date.date(): if now.date() == date.date():
date_string = _('Today') date_string = _("Today")
time_string = formats.date_format(date, format='TIME_FORMAT') time_string = formats.date_format(date, format="TIME_FORMAT")
elif now.year == date.year and formats.get_format( elif (
'SHORT_MONTH_DAY_FORMAT') != 'SHORT_MONTH_DAY_FORMAT': now.year == date.year
and formats.get_format("SHORT_MONTH_DAY_FORMAT") != "SHORT_MONTH_DAY_FORMAT"
):
# Use the custom `SHORT_MONTH_DAY_FORMAT` format if available for the # Use the custom `SHORT_MONTH_DAY_FORMAT` format if available for the
# current locale. # current locale.
date_string = formats.date_format(date, date_string = formats.date_format(date, format="SHORT_MONTH_DAY_FORMAT")
format='SHORT_MONTH_DAY_FORMAT') time_string = formats.date_format(date, format="TIME_FORMAT")
time_string = formats.date_format(date, format='TIME_FORMAT')
if not date_string: if not date_string:
date_string = formats.date_format(date, format='SHORT_DATETIME_FORMAT') date_string = formats.date_format(date, format="SHORT_DATETIME_FORMAT")
if date_string and time_string: if date_string and time_string:
datetime_string = _('{}, {}').format(date_string, time_string) datetime_string = _("{}, {}").format(date_string, time_string)
else: else:
datetime_string = date_string datetime_string = date_string

View File

@ -17,18 +17,18 @@ def child_age_string(birth_date):
:return: a string representation of time since `birth_date`. :return: a string representation of time since `birth_date`.
""" """
if not birth_date: if not birth_date:
return '' return ""
# Return "0 days" for anything under one day. # Return "0 days" for anything under one day.
elif timezone.localdate() - birth_date < timezone.timedelta(days=1): elif timezone.localdate() - birth_date < timezone.timedelta(days=1):
return _('0 days') return _("0 days")
try: try:
return timesince.timesince(birth_date, depth=1) return timesince.timesince(birth_date, depth=1)
except (ValueError, TypeError): except (ValueError, TypeError):
return '' return ""
@register.filter @register.filter
def duration_string(duration, precision='s'): def duration_string(duration, precision="s"):
""" """
Format a duration (e.g. "2 hours, 3 minutes, 35 seconds"). Format a duration (e.g. "2 hours, 3 minutes, 35 seconds").
:param duration: a timedetla instance. :param duration: a timedetla instance.
@ -37,11 +37,11 @@ def duration_string(duration, precision='s'):
:returns: a string representation of the duration. :returns: a string representation of the duration.
""" """
if not duration: if not duration:
return '' return ""
try: try:
return utils.duration_string(duration, precision) return utils.duration_string(duration, precision)
except (ValueError, TypeError): except (ValueError, TypeError):
return '' return ""
@register.filter @register.filter

View File

@ -8,7 +8,7 @@ from core.models import Timer
register = template.Library() register = template.Library()
@register.inclusion_tag('core/timer_nav.html', takes_context=True) @register.inclusion_tag("core/timer_nav.html", takes_context=True)
def timer_nav(context, active=True): def timer_nav(context, active=True):
""" """
Get a list of active Timer instances to include in the nav menu. Get a list of active Timer instances to include in the nav menu.
@ -16,17 +16,17 @@ def timer_nav(context, active=True):
:param active: the state of Timers to filter. :param active: the state of Timers to filter.
:returns: a dictionary with timers data. :returns: a dictionary with timers data.
""" """
request = context['request'] or None request = context["request"] or None
timers = Timer.objects.filter(active=active) timers = Timer.objects.filter(active=active)
perms = context['perms'] or None perms = context["perms"] or None
# The 'next' parameter is currently not used. # The 'next' parameter is currently not used.
return {'timers': timers, 'perms': perms, 'next': request.path} return {"timers": timers, "perms": perms, "next": request.path}
@register.simple_tag(takes_context=True) @register.simple_tag(takes_context=True)
def instance_add_url(context, url_name): def instance_add_url(context, url_name):
timer = context['timer'] timer = context["timer"]
url = '{}?timer={}'.format(reverse(url_name), timer.id) url = "{}?timer={}".format(reverse(url_name), timer.id)
if timer.child: if timer.child:
url += '&child={}'.format(timer.child.slug) url += "&child={}".format(timer.child.slug)
return url return url

View File

@ -20,35 +20,29 @@ class FormsTestCaseBase(TestCase):
def setUpClass(cls): def setUpClass(cls):
super(FormsTestCaseBase, cls).setUpClass() super(FormsTestCaseBase, cls).setUpClass()
fake = Factory.create() fake = Factory.create()
call_command('migrate', verbosity=0) call_command("migrate", verbosity=0)
cls.c = HttpClient() cls.c = HttpClient()
fake_user = fake.simple_profile() fake_user = fake.simple_profile()
credentials = { credentials = {"username": fake_user["username"], "password": fake.password()}
'username': fake_user['username'], cls.user = User.objects.create_user(is_superuser=True, **credentials)
'password': fake.password()
}
cls.user = User.objects.create_user(
is_superuser=True, **credentials)
cls.c.login(**credentials) cls.c.login(**credentials)
cls.child = models.Child.objects.create( cls.child = models.Child.objects.create(
first_name='Child', first_name="Child", last_name="One", birth_date=timezone.localdate()
last_name='One',
birth_date=timezone.localdate()
) )
@staticmethod @staticmethod
def localdate_string(datetime=None): def localdate_string(datetime=None):
"""Converts an object to a local date string for form input.""" """Converts an object to a local date string for form input."""
date_format = get_format('DATE_INPUT_FORMATS')[0] date_format = get_format("DATE_INPUT_FORMATS")[0]
return timezone.localdate(datetime).strftime(date_format) return timezone.localdate(datetime).strftime(date_format)
@staticmethod @staticmethod
def localtime_string(datetime=None): def localtime_string(datetime=None):
"""Converts an object to a local time string for form input.""" """Converts an object to a local time string for form input."""
datetime_format = get_format('DATETIME_INPUT_FORMATS')[0] datetime_format = get_format("DATETIME_INPUT_FORMATS")[0]
return timezone.localtime(datetime).strftime(datetime_format) return timezone.localtime(datetime).strftime(datetime_format)
@ -57,107 +51,98 @@ class InitialValuesTestCase(FormsTestCaseBase):
def setUpClass(cls): def setUpClass(cls):
super(InitialValuesTestCase, cls).setUpClass() super(InitialValuesTestCase, cls).setUpClass()
cls.timer = models.Timer.objects.create( cls.timer = models.Timer.objects.create(
user=cls.user, user=cls.user, start=timezone.localtime() - timezone.timedelta(minutes=30)
start=timezone.localtime() - timezone.timedelta(minutes=30)
) )
def test_child_with_one_child(self): def test_child_with_one_child(self):
page = self.c.get('/sleep/add/') page = self.c.get("/sleep/add/")
self.assertEqual(page.context['form'].initial['child'], self.child) self.assertEqual(page.context["form"].initial["child"], self.child)
def test_child_with_parameter(self): def test_child_with_parameter(self):
child_two = models.Child.objects.create( child_two = models.Child.objects.create(
first_name='Child', first_name="Child", last_name="Two", birth_date=timezone.localdate()
last_name='Two',
birth_date=timezone.localdate()
) )
page = self.c.get('/sleep/add/') page = self.c.get("/sleep/add/")
self.assertTrue('child' not in page.context['form'].initial) self.assertTrue("child" not in page.context["form"].initial)
page = self.c.get('/sleep/add/?child={}'.format(self.child.slug)) page = self.c.get("/sleep/add/?child={}".format(self.child.slug))
self.assertEqual(page.context['form'].initial['child'], self.child) self.assertEqual(page.context["form"].initial["child"], self.child)
page = self.c.get('/sleep/add/?child={}'.format(child_two.slug)) page = self.c.get("/sleep/add/?child={}".format(child_two.slug))
self.assertEqual(page.context['form'].initial['child'], child_two) self.assertEqual(page.context["form"].initial["child"], child_two)
def test_feeding_type(self): def test_feeding_type(self):
child_two = models.Child.objects.create( child_two = models.Child.objects.create(
first_name='Child', first_name="Child", last_name="Two", birth_date=timezone.localdate()
last_name='Two',
birth_date=timezone.localdate()
) )
child_three = models.Child.objects.create( child_three = models.Child.objects.create(
first_name='Child', first_name="Child", last_name="Three", birth_date=timezone.localdate()
last_name='Three',
birth_date=timezone.localdate()
) )
start_time = timezone.localtime() - timezone.timedelta(hours=4) start_time = timezone.localtime() - timezone.timedelta(hours=4)
end_time = timezone.localtime() - timezone.timedelta(hours=3, end_time = timezone.localtime() - timezone.timedelta(hours=3, minutes=30)
minutes=30)
f_one = models.Feeding.objects.create( f_one = models.Feeding.objects.create(
child=self.child, child=self.child,
start=start_time, start=start_time,
end=end_time, end=end_time,
type='breast milk', type="breast milk",
method='left breast' method="left breast",
) )
f_two = models.Feeding.objects.create( f_two = models.Feeding.objects.create(
child=child_two, child=child_two,
start=start_time, start=start_time,
end=end_time, end=end_time,
type='formula', type="formula",
method='bottle' method="bottle",
) )
f_three = models.Feeding.objects.create( f_three = models.Feeding.objects.create(
child=child_three, child=child_three,
start=start_time, start=start_time,
end=end_time, end=end_time,
type='fortified breast milk', type="fortified breast milk",
method='bottle' method="bottle",
) )
page = self.c.get('/feedings/add/') page = self.c.get("/feedings/add/")
self.assertTrue('type' not in page.context['form'].initial) self.assertTrue("type" not in page.context["form"].initial)
page = self.c.get('/feedings/add/?child={}'.format(self.child.slug)) page = self.c.get("/feedings/add/?child={}".format(self.child.slug))
self.assertEqual(page.context['form'].initial['type'], f_one.type) self.assertEqual(page.context["form"].initial["type"], f_one.type)
self.assertFalse('method' in page.context['form'].initial) self.assertFalse("method" in page.context["form"].initial)
page = self.c.get('/feedings/add/?child={}'.format(child_two.slug)) page = self.c.get("/feedings/add/?child={}".format(child_two.slug))
self.assertEqual(page.context['form'].initial['type'], f_two.type) self.assertEqual(page.context["form"].initial["type"], f_two.type)
self.assertEqual(page.context['form'].initial['method'], f_two.method) self.assertEqual(page.context["form"].initial["method"], f_two.method)
page = self.c.get('/feedings/add/?child={}'.format(child_three.slug)) page = self.c.get("/feedings/add/?child={}".format(child_three.slug))
self.assertEqual(page.context['form'].initial['type'], f_three.type) self.assertEqual(page.context["form"].initial["type"], f_three.type)
self.assertEqual(page.context['form'].initial['method'], self.assertEqual(page.context["form"].initial["method"], f_three.method)
f_three.method)
def test_timer_set(self): def test_timer_set(self):
self.timer.stop() self.timer.stop()
page = self.c.get('/sleep/add/') page = self.c.get("/sleep/add/")
self.assertTrue('start' not in page.context['form'].initial) self.assertTrue("start" not in page.context["form"].initial)
self.assertTrue('end' not in page.context['form'].initial) self.assertTrue("end" not in page.context["form"].initial)
page = self.c.get('/sleep/add/?timer={}'.format(self.timer.id)) page = self.c.get("/sleep/add/?timer={}".format(self.timer.id))
self.assertEqual(page.context['form'].initial['start'], self.assertEqual(page.context["form"].initial["start"], self.timer.start)
self.timer.start) self.assertEqual(page.context["form"].initial["end"], self.timer.end)
self.assertEqual(page.context['form'].initial['end'], self.timer.end)
def test_timer_stop_on_save(self): def test_timer_stop_on_save(self):
end = timezone.localtime() end = timezone.localtime()
params = { params = {
'child': self.child.id, "child": self.child.id,
'start': self.localtime_string(self.timer.start), "start": self.localtime_string(self.timer.start),
'end': self.localtime_string(end) "end": self.localtime_string(end),
} }
page = self.c.post('/sleep/add/?timer={}'.format(self.timer.id), page = self.c.post(
params, follow=True) "/sleep/add/?timer={}".format(self.timer.id), params, follow=True
)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.timer.refresh_from_db() self.timer.refresh_from_db()
self.assertFalse(self.timer.active) self.assertFalse(self.timer.active)
self.assertEqual(self.localtime_string(self.timer.end), params['end']) self.assertEqual(self.localtime_string(self.timer.end), params["end"])
class ChildFormsTestCase(FormsTestCaseBase): class ChildFormsTestCase(FormsTestCaseBase):
@ -168,40 +153,44 @@ class ChildFormsTestCase(FormsTestCaseBase):
def test_add(self): def test_add(self):
params = { params = {
'first_name': 'Child', "first_name": "Child",
'last_name': 'Two', "last_name": "Two",
'birth_date': timezone.localdate() "birth_date": timezone.localdate(),
} }
page = self.c.post('/children/add/', params, follow=True) page = self.c.post("/children/add/", params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertContains(page, 'Child entry added') self.assertContains(page, "Child entry added")
def test_edit(self): def test_edit(self):
params = { params = {
'first_name': 'Name', "first_name": "Name",
'last_name': 'Changed', "last_name": "Changed",
'birth_date': self.child.birth_date "birth_date": self.child.birth_date,
} }
page = self.c.post('/children/{}/edit/'.format(self.child.slug), page = self.c.post(
params, follow=True) "/children/{}/edit/".format(self.child.slug), params, follow=True
)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.child.refresh_from_db() self.child.refresh_from_db()
self.assertEqual(self.child.last_name, params['last_name']) self.assertEqual(self.child.last_name, params["last_name"])
self.assertContains(page, 'Child entry updated') self.assertContains(page, "Child entry updated")
def test_delete(self): def test_delete(self):
params = {'confirm_name': 'Incorrect'} params = {"confirm_name": "Incorrect"}
page = self.c.post('/children/{}/delete/'.format(self.child.slug), page = self.c.post(
params, follow=True) "/children/{}/delete/".format(self.child.slug), params, follow=True
)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertFormError(page, 'form', 'confirm_name', self.assertFormError(
'Name does not match child name.') page, "form", "confirm_name", "Name does not match child name."
)
params['confirm_name'] = str(self.child) params["confirm_name"] = str(self.child)
page = self.c.post('/children/{}/delete/'.format(self.child.slug), page = self.c.post(
params, follow=True) "/children/{}/delete/".format(self.child.slug), params, follow=True
)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertContains(page, 'Child entry deleted') self.assertContains(page, "Child entry deleted")
class DiaperChangeFormsTestCase(FormsTestCaseBase): class DiaperChangeFormsTestCase(FormsTestCaseBase):
@ -213,55 +202,48 @@ class DiaperChangeFormsTestCase(FormsTestCaseBase):
time=timezone.localtime(), time=timezone.localtime(),
wet=True, wet=True,
solid=True, solid=True,
color='black', color="black",
amount=0.45 amount=0.45,
) )
def test_add(self): def test_add(self):
child = models.Child.objects.first() child = models.Child.objects.first()
params = { params = {
'child': child.id, "child": child.id,
'time': self.localtime_string(), "time": self.localtime_string(),
'color': 'black', "color": "black",
'amount': 0.45 "amount": 0.45,
} }
page = self.c.post('/changes/add/', params) page = self.c.post("/changes/add/", params)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertFormError(page, 'form', None, self.assertFormError(page, "form", None, "Wet and/or solid is required.")
'Wet and/or solid is required.')
params.update({'wet': 1, 'solid': 1, 'color': 'black'}) params.update({"wet": 1, "solid": 1, "color": "black"})
page = self.c.post('/changes/add/', params, follow=True) page = self.c.post("/changes/add/", params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertContains( self.assertContains(page, "Diaper Change entry for {} added".format(str(child)))
page,
'Diaper Change entry for {} added'.format(str(child))
)
def test_edit(self): def test_edit(self):
params = { params = {
'child': self.change.child.id, "child": self.change.child.id,
'time': self.localtime_string(), "time": self.localtime_string(),
'wet': self.change.wet, "wet": self.change.wet,
'solid': self.change.solid, "solid": self.change.solid,
'color': self.change.color, "color": self.change.color,
'amount': 1.23 "amount": 1.23,
} }
page = self.c.post('/changes/{}/'.format(self.change.id), page = self.c.post("/changes/{}/".format(self.change.id), params, follow=True)
params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.change.refresh_from_db() self.change.refresh_from_db()
self.assertEqual(self.change.amount, params['amount']) self.assertEqual(self.change.amount, params["amount"])
self.assertContains( self.assertContains(
page, page, "Diaper Change entry for {} updated".format(str(self.change.child))
'Diaper Change entry for {} updated'.format(str(self.change.child))
) )
def test_delete(self): def test_delete(self):
page = self.c.post('/changes/{}/delete/'.format(self.change.id), page = self.c.post("/changes/{}/delete/".format(self.change.id), follow=True)
follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertContains(page, 'Diaper Change entry deleted') self.assertContains(page, "Diaper Change entry deleted")
class FeedingFormsTestCase(FormsTestCaseBase): class FeedingFormsTestCase(FormsTestCaseBase):
@ -272,55 +254,49 @@ class FeedingFormsTestCase(FormsTestCaseBase):
child=cls.child, child=cls.child,
start=timezone.localtime() - timezone.timedelta(hours=2), start=timezone.localtime() - timezone.timedelta(hours=2),
end=timezone.localtime() - timezone.timedelta(hours=1, minutes=30), end=timezone.localtime() - timezone.timedelta(hours=1, minutes=30),
type='breast milk', type="breast milk",
method='left breast', method="left breast",
amount=2.5 amount=2.5,
) )
def test_add(self): def test_add(self):
end = timezone.localtime() end = timezone.localtime()
start = end - timezone.timedelta(minutes=30) start = end - timezone.timedelta(minutes=30)
params = { params = {
'child': self.child.id, "child": self.child.id,
'start': self.localtime_string(start), "start": self.localtime_string(start),
'end': self.localtime_string(end), "end": self.localtime_string(end),
'type': 'formula', "type": "formula",
'method': 'bottle', "method": "bottle",
'amount': 0 "amount": 0,
} }
page = self.c.post('/feedings/add/', params, follow=True) page = self.c.post("/feedings/add/", params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertContains( self.assertContains(page, "Feeding entry for {} added".format(str(self.child)))
page,
'Feeding entry for {} added'.format(str(self.child))
)
def test_edit(self): def test_edit(self):
end = timezone.localtime() end = timezone.localtime()
start = end - timezone.timedelta(minutes=30) start = end - timezone.timedelta(minutes=30)
params = { params = {
'child': self.feeding.child.id, "child": self.feeding.child.id,
'start': self.localtime_string(start), "start": self.localtime_string(start),
'end': self.localtime_string(end), "end": self.localtime_string(end),
'type': self.feeding.type, "type": self.feeding.type,
'method': self.feeding.method, "method": self.feeding.method,
'amount': 100 "amount": 100,
} }
page = self.c.post('/feedings/{}/'.format(self.feeding.id), page = self.c.post("/feedings/{}/".format(self.feeding.id), params, follow=True)
params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.feeding.refresh_from_db() self.feeding.refresh_from_db()
self.assertEqual(self.feeding.amount, params['amount']) self.assertEqual(self.feeding.amount, params["amount"])
self.assertContains( self.assertContains(
page, page, "Feeding entry for {} updated".format(str(self.feeding.child))
'Feeding entry for {} updated'.format(str(self.feeding.child))
) )
def test_delete(self): def test_delete(self):
page = self.c.post('/feedings/{}/delete/'.format(self.feeding.id), page = self.c.post("/feedings/{}/delete/".format(self.feeding.id), follow=True)
follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertContains(page, 'Feeding entry deleted') self.assertContains(page, "Feeding entry deleted")
class SleepFormsTestCase(FormsTestCaseBase): class SleepFormsTestCase(FormsTestCaseBase):
@ -330,7 +306,7 @@ class SleepFormsTestCase(FormsTestCaseBase):
cls.sleep = models.Sleep.objects.create( cls.sleep = models.Sleep.objects.create(
child=cls.child, child=cls.child,
start=timezone.localtime() - timezone.timedelta(hours=6), start=timezone.localtime() - timezone.timedelta(hours=6),
end=timezone.localtime() - timezone.timedelta(hours=4) end=timezone.localtime() - timezone.timedelta(hours=4),
) )
def test_add(self): def test_add(self):
@ -340,44 +316,35 @@ class SleepFormsTestCase(FormsTestCaseBase):
end = timezone.localtime() end = timezone.localtime()
start = end - timezone.timedelta(minutes=2) start = end - timezone.timedelta(minutes=2)
params = { params = {
'child': self.child.id, "child": self.child.id,
'start': self.localtime_string(start), "start": self.localtime_string(start),
'end': self.localtime_string(end), "end": self.localtime_string(end),
} }
page = self.c.post('/sleep/add/', params, follow=True) page = self.c.post("/sleep/add/", params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertContains( self.assertContains(page, "Sleep entry for {} added".format(str(self.child)))
page,
'Sleep entry for {} added'.format(str(self.child))
)
def test_edit(self): def test_edit(self):
end = timezone.localtime() end = timezone.localtime()
start = end - timezone.timedelta(minutes=2) start = end - timezone.timedelta(minutes=2)
params = { params = {
'child': self.sleep.child.id, "child": self.sleep.child.id,
'start': self.localtime_string(start), "start": self.localtime_string(start),
'end': self.localtime_string(end), "end": self.localtime_string(end),
} }
page = self.c.post('/sleep/{}/'.format(self.sleep.id), page = self.c.post("/sleep/{}/".format(self.sleep.id), params, follow=True)
params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.sleep.refresh_from_db() self.sleep.refresh_from_db()
self.assertEqual( self.assertEqual(self.localtime_string(self.sleep.end), params["end"])
self.localtime_string(self.sleep.end),
params['end']
)
self.assertContains( self.assertContains(
page, page, "Sleep entry for {} updated".format(str(self.sleep.child))
'Sleep entry for {} updated'.format(str(self.sleep.child))
) )
def test_delete(self): def test_delete(self):
page = self.c.post('/sleep/{}/delete/'.format(self.sleep.id), page = self.c.post("/sleep/{}/delete/".format(self.sleep.id), follow=True)
follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertContains(page, 'Sleep entry deleted') self.assertContains(page, "Sleep entry deleted")
class TemperatureFormsTestCase(FormsTestCaseBase): class TemperatureFormsTestCase(FormsTestCaseBase):
@ -387,44 +354,40 @@ class TemperatureFormsTestCase(FormsTestCaseBase):
cls.temp = models.Temperature.objects.create( cls.temp = models.Temperature.objects.create(
child=cls.child, child=cls.child,
temperature=98.6, temperature=98.6,
time=timezone.localtime() - timezone.timedelta(days=1) time=timezone.localtime() - timezone.timedelta(days=1),
) )
def test_add(self): def test_add(self):
params = { params = {
'child': self.child.id, "child": self.child.id,
'temperature': '98.6', "temperature": "98.6",
'time': self.localtime_string() "time": self.localtime_string(),
} }
page = self.c.post('/temperature/add/', params, follow=True) page = self.c.post("/temperature/add/", params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertContains( self.assertContains(
page, page, "Temperature entry for {} added".format(str(self.child))
'Temperature entry for {} added'.format(str(self.child))
) )
def test_edit(self): def test_edit(self):
params = { params = {
'child': self.temp.child.id, "child": self.temp.child.id,
'temperature': self.temp.temperature + 2, "temperature": self.temp.temperature + 2,
'time': self.localtime_string() "time": self.localtime_string(),
} }
page = self.c.post('/temperature/{}/'.format(self.temp.id), page = self.c.post("/temperature/{}/".format(self.temp.id), params, follow=True)
params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.temp.refresh_from_db() self.temp.refresh_from_db()
self.assertEqual(self.temp.temperature, params['temperature']) self.assertEqual(self.temp.temperature, params["temperature"])
self.assertContains( self.assertContains(
page, page, "Temperature entry for {} updated".format(str(self.temp.child))
'Temperature entry for {} updated'.format(str(self.temp.child))
) )
def test_delete(self): def test_delete(self):
page = self.c.post('/temperature/{}/delete/'.format(self.temp.id), page = self.c.post("/temperature/{}/delete/".format(self.temp.id), follow=True)
follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertContains(page, 'Temperature entry deleted') self.assertContains(page, "Temperature entry deleted")
class TummyTimeFormsTestCase(FormsTestCaseBase): class TummyTimeFormsTestCase(FormsTestCaseBase):
@ -434,50 +397,46 @@ class TummyTimeFormsTestCase(FormsTestCaseBase):
cls.tt = models.TummyTime.objects.create( cls.tt = models.TummyTime.objects.create(
child=cls.child, child=cls.child,
start=timezone.localtime() - timezone.timedelta(hours=2), start=timezone.localtime() - timezone.timedelta(hours=2),
end=timezone.localtime() - timezone.timedelta(hours=1, minutes=50) end=timezone.localtime() - timezone.timedelta(hours=1, minutes=50),
) )
def test_add(self): def test_add(self):
end = timezone.localtime() end = timezone.localtime()
start = end - timezone.timedelta(minutes=2) start = end - timezone.timedelta(minutes=2)
params = { params = {
'child': self.child.id, "child": self.child.id,
'start': self.localtime_string(start), "start": self.localtime_string(start),
'end': self.localtime_string(end), "end": self.localtime_string(end),
'milestone': '' "milestone": "",
} }
page = self.c.post('/tummy-time/add/', params, follow=True) page = self.c.post("/tummy-time/add/", params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertContains( self.assertContains(
page, page, "Tummy Time entry for {} added".format(str(self.child))
'Tummy Time entry for {} added'.format(str(self.child))
) )
def test_edit(self): def test_edit(self):
end = timezone.localtime() end = timezone.localtime()
start = end - timezone.timedelta(minutes=1, seconds=32) start = end - timezone.timedelta(minutes=1, seconds=32)
params = { params = {
'child': self.tt.child.id, "child": self.tt.child.id,
'start': self.localtime_string(start), "start": self.localtime_string(start),
'end': self.localtime_string(end), "end": self.localtime_string(end),
'milestone': 'Moved head!' "milestone": "Moved head!",
} }
page = self.c.post('/tummy-time/{}/'.format(self.tt.id), page = self.c.post("/tummy-time/{}/".format(self.tt.id), params, follow=True)
params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.tt.refresh_from_db() self.tt.refresh_from_db()
self.assertEqual(self.tt.milestone, params['milestone']) self.assertEqual(self.tt.milestone, params["milestone"])
self.assertContains( self.assertContains(
page, page, "Tummy Time entry for {} updated".format(str(self.tt.child))
'Tummy Time entry for {} updated'.format(str(self.tt.child))
) )
def test_delete(self): def test_delete(self):
page = self.c.post('/tummy-time/{}/delete/'.format(self.tt.id), page = self.c.post("/tummy-time/{}/delete/".format(self.tt.id), follow=True)
follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertContains(page, 'Tummy Time entry deleted') self.assertContains(page, "Tummy Time entry deleted")
class TimerFormsTestCase(FormsTestCaseBase): class TimerFormsTestCase(FormsTestCaseBase):
@ -488,49 +447,47 @@ class TimerFormsTestCase(FormsTestCaseBase):
def test_add(self): def test_add(self):
params = { params = {
'child': self.child.id, "child": self.child.id,
'name': 'Test Timer', "name": "Test Timer",
'start': self.localtime_string() "start": self.localtime_string(),
} }
page = self.c.post('/timers/add/', params, follow=True) page = self.c.post("/timers/add/", params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertContains(page, params['name']) self.assertContains(page, params["name"])
self.assertContains(page, params['child']) self.assertContains(page, params["child"])
def test_edit(self): def test_edit(self):
start_time = self.timer.start - timezone.timedelta(hours=1) start_time = self.timer.start - timezone.timedelta(hours=1)
params = { params = {"name": "New Timer Name", "start": self.localtime_string(start_time)}
'name': 'New Timer Name', page = self.c.post(
'start': self.localtime_string(start_time) "/timers/{}/edit/".format(self.timer.id), params, follow=True
} )
page = self.c.post('/timers/{}/edit/'.format(self.timer.id), params,
follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertContains(page, params['name']) self.assertContains(page, params["name"])
self.timer.refresh_from_db() self.timer.refresh_from_db()
self.assertEqual( self.assertEqual(self.localtime_string(self.timer.start), params["start"])
self.localtime_string(self.timer.start), params['start'])
def test_edit_stopped(self): def test_edit_stopped(self):
self.timer.stop() self.timer.stop()
params = { params = {
'name': 'Edit stopped timer', "name": "Edit stopped timer",
'start': self.localtime_string(self.timer.start), "start": self.localtime_string(self.timer.start),
'end': self.localtime_string(self.timer.end), "end": self.localtime_string(self.timer.end),
} }
page = self.c.post('/timers/{}/edit/'.format(self.timer.id), params, page = self.c.post(
follow=True) "/timers/{}/edit/".format(self.timer.id), params, follow=True
)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
def test_delete_inactive(self): def test_delete_inactive(self):
models.Timer.objects.create(user=self.user) models.Timer.objects.create(user=self.user)
self.assertEqual(models.Timer.objects.count(), 2) self.assertEqual(models.Timer.objects.count(), 2)
self.timer.stop() self.timer.stop()
page = self.c.post('/timers/delete-inactive/', follow=True) page = self.c.post("/timers/delete-inactive/", follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
messages = list(page.context['messages']) messages = list(page.context["messages"])
self.assertEqual(len(messages), 1) self.assertEqual(len(messages), 1)
self.assertEqual(str(messages[0]), 'All inactive timers deleted.') self.assertEqual(str(messages[0]), "All inactive timers deleted.")
self.assertEqual(models.Timer.objects.count(), 1) self.assertEqual(models.Timer.objects.count(), 1)
@ -538,51 +495,50 @@ class ValidationsTestCase(FormsTestCaseBase):
def test_validate_date(self): def test_validate_date(self):
future = timezone.localtime() + timezone.timedelta(days=1) future = timezone.localtime() + timezone.timedelta(days=1)
params = { params = {
'child': self.child, "child": self.child,
'weight': '8.5', "weight": "8.5",
'date': self.localdate_string(future) "date": self.localdate_string(future),
} }
entry = models.Weight.objects.create(**params) entry = models.Weight.objects.create(**params)
page = self.c.post('/weight/{}/'.format(entry.id), params, follow=True) page = self.c.post("/weight/{}/".format(entry.id), params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertFormError(page, 'form', 'date', self.assertFormError(page, "form", "date", "Date can not be in the future.")
'Date can not be in the future.')
def test_validate_duration(self): def test_validate_duration(self):
end = timezone.localtime() - timezone.timedelta(minutes=10) end = timezone.localtime() - timezone.timedelta(minutes=10)
start = end + timezone.timedelta(minutes=5) start = end + timezone.timedelta(minutes=5)
params = { params = {
'child': self.child, "child": self.child,
'start': self.localtime_string(start), "start": self.localtime_string(start),
'end': self.localtime_string(end), "end": self.localtime_string(end),
'milestone': '' "milestone": "",
} }
page = self.c.post('/tummy-time/add/', params, follow=True) page = self.c.post("/tummy-time/add/", params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertFormError(page, 'form', None, self.assertFormError(
'Start time must come before end time.') page, "form", None, "Start time must come before end time."
)
start = end - timezone.timedelta(weeks=53) start = end - timezone.timedelta(weeks=53)
params['start'] = self.localtime_string(start) params["start"] = self.localtime_string(start)
page = self.c.post('/tummy-time/add/', params, follow=True) page = self.c.post("/tummy-time/add/", params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertFormError(page, 'form', None, 'Duration too long.') self.assertFormError(page, "form", None, "Duration too long.")
def test_validate_time(self): def test_validate_time(self):
future = timezone.localtime() + timezone.timedelta(hours=1) future = timezone.localtime() + timezone.timedelta(hours=1)
params = { params = {
'child': self.child, "child": self.child,
'start': self.localtime_string(), "start": self.localtime_string(),
'end': self.localtime_string(future), "end": self.localtime_string(future),
'milestone': '' "milestone": "",
} }
page = self.c.post('/tummy-time/add/', params, follow=True) page = self.c.post("/tummy-time/add/", params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertFormError(page, 'form', 'end', self.assertFormError(page, "form", "end", "Date/time can not be in the future.")
'Date/time can not be in the future.')
def test_validate_unique_period(self): def test_validate_unique_period(self):
entry = models.TummyTime.objects.create( entry = models.TummyTime.objects.create(
@ -594,19 +550,17 @@ class ValidationsTestCase(FormsTestCaseBase):
start = entry.start - timezone.timedelta(minutes=2) start = entry.start - timezone.timedelta(minutes=2)
end = entry.end + timezone.timedelta(minutes=2) end = entry.end + timezone.timedelta(minutes=2)
params = { params = {
'child': entry.child.id, "child": entry.child.id,
'start': self.localtime_string(start), "start": self.localtime_string(start),
'end': self.localtime_string(end), "end": self.localtime_string(end),
'milestone': '' "milestone": "",
} }
page = self.c.post('/tummy-time/add/', params, follow=True) page = self.c.post("/tummy-time/add/", params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertFormError( self.assertFormError(
page, page, "form", None, "Another entry intersects the specified time period."
'form', )
None,
'Another entry intersects the specified time period.')
class WeightFormsTest(FormsTestCaseBase): class WeightFormsTest(FormsTestCaseBase):
@ -616,41 +570,35 @@ class WeightFormsTest(FormsTestCaseBase):
cls.weight = models.Weight.objects.create( cls.weight = models.Weight.objects.create(
child=cls.child, child=cls.child,
weight=8, weight=8,
date=timezone.localdate() - timezone.timedelta(days=2) date=timezone.localdate() - timezone.timedelta(days=2),
) )
def test_add(self): def test_add(self):
params = { params = {
'child': self.child.id, "child": self.child.id,
'weight': 8.5, "weight": 8.5,
'date': self.localdate_string() "date": self.localdate_string(),
} }
page = self.c.post('/weight/add/', params, follow=True) page = self.c.post("/weight/add/", params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertContains( self.assertContains(page, "Weight entry for {} added".format(str(self.child)))
page,
'Weight entry for {} added'.format(str(self.child))
)
def test_edit(self): def test_edit(self):
params = { params = {
'child': self.weight.child.id, "child": self.weight.child.id,
'weight': self.weight.weight + 1, "weight": self.weight.weight + 1,
'date': self.localdate_string() "date": self.localdate_string(),
} }
page = self.c.post('/weight/{}/'.format(self.weight.id), page = self.c.post("/weight/{}/".format(self.weight.id), params, follow=True)
params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.weight.refresh_from_db() self.weight.refresh_from_db()
self.assertEqual(self.weight.weight, params['weight']) self.assertEqual(self.weight.weight, params["weight"])
self.assertContains( self.assertContains(
page, page, "Weight entry for {} updated".format(str(self.weight.child))
'Weight entry for {} updated'.format(str(self.weight.child))
) )
def test_delete(self): def test_delete(self):
page = self.c.post('/weight/{}/delete/'.format(self.weight.id), page = self.c.post("/weight/{}/delete/".format(self.weight.id), follow=True)
follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertContains(page, 'Weight entry deleted') self.assertContains(page, "Weight entry deleted")

View File

@ -11,25 +11,27 @@ from core import admin, models
class ImportTestCase(TestCase): class ImportTestCase(TestCase):
base_path = os.path.dirname(__file__) + '/import/' base_path = os.path.dirname(__file__) + "/import/"
admin_module = importlib.import_module('core.admin') admin_module = importlib.import_module("core.admin")
model_module = importlib.import_module('core.models') model_module = importlib.import_module("core.models")
def setUp(self): def setUp(self):
call_command('migrate', verbosity=0) call_command("migrate", verbosity=0)
# The data to be imported uses 2020-02-10 as a basis and Child ID 1. # The data to be imported uses 2020-02-10 as a basis and Child ID 1.
birth_date = datetime.date(year=2020, month=2, day=10) birth_date = datetime.date(year=2020, month=2, day=10)
models.Child.objects.create( models.Child.objects.create(
first_name='Child', last_name='One', birth_date=birth_date).save() first_name="Child", last_name="One", birth_date=birth_date
).save()
def get_dataset(self, model_name): def get_dataset(self, model_name):
file = open(self.base_path + model_name + '.csv') file = open(self.base_path + model_name + ".csv")
return tablib.Dataset().load(file.read()) return tablib.Dataset().load(file.read())
def import_data(self, model, count): def import_data(self, model, count):
dataset = self.get_dataset(model._meta.model_name) dataset = self.get_dataset(model._meta.model_name)
resource_class = getattr( resource_class = getattr(
self.admin_module, model.__name__ + 'ImportExportResource') self.admin_module, model.__name__ + "ImportExportResource"
)
resource = resource_class() resource = resource_class()
result = resource.import_data(dataset, dry_run=False) result = resource.import_data(dataset, dry_run=False)
self.assertFalse(result.has_validation_errors()) self.assertFalse(result.has_validation_errors())
@ -61,7 +63,7 @@ class ImportTestCase(TestCase):
self.import_data(models.Weight, 5) self.import_data(models.Weight, 5)
def test_invalid_child(self): def test_invalid_child(self):
dataset = self.get_dataset('diaperchange-invalid-child') dataset = self.get_dataset("diaperchange-invalid-child")
resource = admin.DiaperChangeImportExportResource() resource = admin.DiaperChangeImportExportResource()
result = resource.import_data(dataset, dry_run=False) result = resource.import_data(dataset, dry_run=False)
self.assertTrue(result.has_validation_errors()) self.assertTrue(result.has_validation_errors())

View File

@ -9,32 +9,26 @@ from core import models
class ChildTestCase(TestCase): class ChildTestCase(TestCase):
def setUp(self): def setUp(self):
call_command('migrate', verbosity=0) call_command("migrate", verbosity=0)
def test_child_create(self): def test_child_create(self):
child = models.Child.objects.create( child = models.Child.objects.create(
first_name='First', first_name="First", last_name="Last", birth_date=timezone.localdate()
last_name='Last',
birth_date=timezone.localdate()
) )
self.assertEqual(child, models.Child.objects.get(first_name='First')) self.assertEqual(child, models.Child.objects.get(first_name="First"))
self.assertEqual(child.slug, 'first-last') self.assertEqual(child.slug, "first-last")
self.assertEqual(str(child), 'First Last') self.assertEqual(str(child), "First Last")
self.assertEqual(child.name(), 'First Last') self.assertEqual(child.name(), "First Last")
self.assertEqual(child.name(reverse=True), 'Last, First') self.assertEqual(child.name(reverse=True), "Last, First")
def test_child_count(self): def test_child_count(self):
self.assertEqual(models.Child.count(), 0) self.assertEqual(models.Child.count(), 0)
models.Child.objects.create( models.Child.objects.create(
first_name='First 1', first_name="First 1", last_name="Last 1", birth_date=timezone.localdate()
last_name='Last 1',
birth_date=timezone.localdate()
) )
self.assertEqual(models.Child.count(), 1) self.assertEqual(models.Child.count(), 1)
child = models.Child.objects.create( child = models.Child.objects.create(
first_name='First 2', first_name="First 2", last_name="Last 2", birth_date=timezone.localdate()
last_name='Last 2',
birth_date=timezone.localdate()
) )
self.assertEqual(models.Child.count(), 2) self.assertEqual(models.Child.count(), 2)
child.delete() child.delete()
@ -43,42 +37,37 @@ class ChildTestCase(TestCase):
class DiaperChangeTestCase(TestCase): class DiaperChangeTestCase(TestCase):
def setUp(self): def setUp(self):
call_command('migrate', verbosity=0) call_command("migrate", verbosity=0)
self.child = models.Child.objects.create( self.child = models.Child.objects.create(
first_name='First', first_name="First", last_name="Last", birth_date=timezone.localdate()
last_name='Last',
birth_date=timezone.localdate()
) )
self.change = models.DiaperChange.objects.create( self.change = models.DiaperChange.objects.create(
child=self.child, child=self.child,
time=timezone.localtime() - timezone.timedelta(days=1), time=timezone.localtime() - timezone.timedelta(days=1),
wet=1, wet=1,
solid=1, solid=1,
color='black', color="black",
amount=1.25 amount=1.25,
) )
def test_diaperchange_create(self): def test_diaperchange_create(self):
self.assertEqual(self.change, models.DiaperChange.objects.first()) self.assertEqual(self.change, models.DiaperChange.objects.first())
self.assertEqual(str(self.change), 'Diaper Change') self.assertEqual(str(self.change), "Diaper Change")
self.assertEqual(self.change.child, self.child) self.assertEqual(self.change.child, self.child)
self.assertTrue(self.change.wet) self.assertTrue(self.change.wet)
self.assertTrue(self.change.solid) self.assertTrue(self.change.solid)
self.assertEqual(self.change.color, 'black') self.assertEqual(self.change.color, "black")
self.assertEqual(self.change.amount, 1.25) self.assertEqual(self.change.amount, 1.25)
def test_diaperchange_attributes(self): def test_diaperchange_attributes(self):
self.assertListEqual( self.assertListEqual(self.change.attributes(), ["Wet", "Solid", "Black"])
self.change.attributes(), ['Wet', 'Solid', 'Black'])
class FeedingTestCase(TestCase): class FeedingTestCase(TestCase):
def setUp(self): def setUp(self):
call_command('migrate', verbosity=0) call_command("migrate", verbosity=0)
self.child = models.Child.objects.create( self.child = models.Child.objects.create(
first_name='First', first_name="First", last_name="Last", birth_date=timezone.localdate()
last_name='Last',
birth_date=timezone.localdate()
) )
def test_feeding_create(self): def test_feeding_create(self):
@ -86,12 +75,12 @@ class FeedingTestCase(TestCase):
child=self.child, child=self.child,
start=timezone.localtime() - timezone.timedelta(minutes=30), start=timezone.localtime() - timezone.timedelta(minutes=30),
end=timezone.localtime(), end=timezone.localtime(),
type='formula', type="formula",
method='bottle', method="bottle",
amount=2 amount=2,
) )
self.assertEqual(feeding, models.Feeding.objects.first()) self.assertEqual(feeding, models.Feeding.objects.first())
self.assertEqual(str(feeding), 'Feeding') self.assertEqual(str(feeding), "Feeding")
self.assertEqual(feeding.duration, feeding.end - feeding.start) self.assertEqual(feeding.duration, feeding.end - feeding.start)
def test_method_both_breasts(self): def test_method_both_breasts(self):
@ -99,37 +88,34 @@ class FeedingTestCase(TestCase):
child=self.child, child=self.child,
start=timezone.localtime() - timezone.timedelta(minutes=30), start=timezone.localtime() - timezone.timedelta(minutes=30),
end=timezone.localtime(), end=timezone.localtime(),
type='breast milk', type="breast milk",
method='both breasts' method="both breasts",
) )
self.assertEqual(feeding, models.Feeding.objects.first()) self.assertEqual(feeding, models.Feeding.objects.first())
self.assertEqual(str(feeding), 'Feeding') self.assertEqual(str(feeding), "Feeding")
self.assertEqual(feeding.method, 'both breasts') self.assertEqual(feeding.method, "both breasts")
class NoteTestCase(TestCase): class NoteTestCase(TestCase):
def setUp(self): def setUp(self):
call_command('migrate', verbosity=0) call_command("migrate", verbosity=0)
self.child = models.Child.objects.create( self.child = models.Child.objects.create(
first_name='First', first_name="First", last_name="Last", birth_date=timezone.localdate()
last_name='Last',
birth_date=timezone.localdate()
) )
def test_note_create(self): def test_note_create(self):
note = models.Note.objects.create( note = models.Note.objects.create(
child=self.child, note='Note', time=timezone.localtime()) child=self.child, note="Note", time=timezone.localtime()
)
self.assertEqual(note, models.Note.objects.first()) self.assertEqual(note, models.Note.objects.first())
self.assertEqual(str(note), 'Note') self.assertEqual(str(note), "Note")
class SleepTestCase(TestCase): class SleepTestCase(TestCase):
def setUp(self): def setUp(self):
call_command('migrate', verbosity=0) call_command("migrate", verbosity=0)
self.child = models.Child.objects.create( self.child = models.Child.objects.create(
first_name='First', first_name="First", last_name="Last", birth_date=timezone.localdate()
last_name='Last',
birth_date=timezone.localdate()
) )
def test_sleep_create(self): def test_sleep_create(self):
@ -139,74 +125,63 @@ class SleepTestCase(TestCase):
end=timezone.localtime(), end=timezone.localtime(),
) )
self.assertEqual(sleep, models.Sleep.objects.first()) self.assertEqual(sleep, models.Sleep.objects.first())
self.assertEqual(str(sleep), 'Sleep') self.assertEqual(str(sleep), "Sleep")
self.assertEqual(sleep.duration, sleep.end - sleep.start) self.assertEqual(sleep.duration, sleep.end - sleep.start)
class TemperatureTestCase(TestCase): class TemperatureTestCase(TestCase):
def setUp(self): def setUp(self):
call_command('migrate', verbosity=0) call_command("migrate", verbosity=0)
self.child = models.Child.objects.create( self.child = models.Child.objects.create(
first_name='First', first_name="First", last_name="Last", birth_date=timezone.localdate()
last_name='Last',
birth_date=timezone.localdate()
) )
self.temp = models.Temperature.objects.create( self.temp = models.Temperature.objects.create(
child=self.child, child=self.child,
time=timezone.localtime() - timezone.timedelta(days=1), time=timezone.localtime() - timezone.timedelta(days=1),
temperature=98.6 temperature=98.6,
) )
def test_temperature_create(self): def test_temperature_create(self):
self.assertEqual(self.temp, models.Temperature.objects.first()) self.assertEqual(self.temp, models.Temperature.objects.first())
self.assertEqual(str(self.temp), 'Temperature') self.assertEqual(str(self.temp), "Temperature")
self.assertEqual(self.temp.temperature, 98.6) self.assertEqual(self.temp.temperature, 98.6)
class TimerTestCase(TestCase): class TimerTestCase(TestCase):
def setUp(self): def setUp(self):
call_command('migrate', verbosity=0) call_command("migrate", verbosity=0)
child = models.Child.objects.create( child = models.Child.objects.create(
first_name='First', first_name="First", last_name="Last", birth_date=timezone.localdate()
last_name='Last',
birth_date=timezone.localdate()
) )
self.user = User.objects.first() self.user = User.objects.first()
self.named = models.Timer.objects.create( self.named = models.Timer.objects.create(
name='Named', name="Named", end=timezone.localtime(), user=self.user, child=child
end=timezone.localtime(),
user=self.user,
child=child
) )
self.unnamed = models.Timer.objects.create( self.unnamed = models.Timer.objects.create(
end=timezone.localtime(), end=timezone.localtime(), user=self.user
user=self.user
) )
def test_timer_create(self): def test_timer_create(self):
self.assertEqual(self.named, models.Timer.objects.get(name='Named')) self.assertEqual(self.named, models.Timer.objects.get(name="Named"))
self.assertEqual(str(self.named), 'Named') self.assertEqual(str(self.named), "Named")
self.assertEqual(self.unnamed, models.Timer.objects.get(name=None)) self.assertEqual(self.unnamed, models.Timer.objects.get(name=None))
self.assertEqual( self.assertEqual(str(self.unnamed), "Timer #{}".format(self.unnamed.id))
str(self.unnamed), 'Timer #{}'.format(self.unnamed.id))
def test_timer_title_with_child(self): def test_timer_title_with_child(self):
self.assertEqual(self.named.title_with_child, str(self.named)) self.assertEqual(self.named.title_with_child, str(self.named))
models.Child.objects.create( models.Child.objects.create(
first_name='Child', first_name="Child", last_name="Two", birth_date=timezone.localdate()
last_name='Two',
birth_date=timezone.localdate()
) )
self.assertEqual( self.assertEqual(
self.named.title_with_child, self.named.title_with_child,
'{} ({})'.format(str(self.named), str(self.named.child)) "{} ({})".format(str(self.named), str(self.named.child)),
) )
def test_timer_user_username(self): def test_timer_user_username(self):
self.assertEqual(self.named.user_username, self.user.get_username()) self.assertEqual(self.named.user_username, self.user.get_username())
self.user.first_name = 'User' self.user.first_name = "User"
self.user.last_name = 'Name' self.user.last_name = "Name"
self.user.save() self.user.save()
self.assertEqual(self.named.user_username, self.user.get_full_name()) self.assertEqual(self.named.user_username, self.user.get_full_name())
@ -222,7 +197,8 @@ class TimerTestCase(TestCase):
self.assertEqual(self.unnamed.end, stop_time) self.assertEqual(self.unnamed.end, stop_time)
self.assertEqual( self.assertEqual(
self.unnamed.duration.seconds, self.unnamed.duration.seconds,
(self.unnamed.end - self.unnamed.start).seconds) (self.unnamed.end - self.unnamed.start).seconds,
)
self.assertFalse(self.unnamed.active) self.assertFalse(self.unnamed.active)
def test_timer_duration(self): def test_timer_duration(self):
@ -232,22 +208,16 @@ class TimerTestCase(TestCase):
timer.save() timer.save()
timer.refresh_from_db() timer.refresh_from_db()
self.assertEqual( self.assertEqual(timer.duration.seconds, timezone.timedelta(minutes=30).seconds)
timer.duration.seconds,
timezone.timedelta(minutes=30).seconds)
timer.stop() timer.stop()
self.assertEqual( self.assertEqual(timer.duration.seconds, timezone.timedelta(minutes=30).seconds)
timer.duration.seconds,
timezone.timedelta(minutes=30).seconds)
class TummyTimeTestCase(TestCase): class TummyTimeTestCase(TestCase):
def setUp(self): def setUp(self):
call_command('migrate', verbosity=0) call_command("migrate", verbosity=0)
self.child = models.Child.objects.create( self.child = models.Child.objects.create(
first_name='First', first_name="First", last_name="Last", birth_date=timezone.localdate()
last_name='Last',
birth_date=timezone.localdate()
) )
def test_tummytime_create(self): def test_tummytime_create(self):
@ -257,6 +227,5 @@ class TummyTimeTestCase(TestCase):
end=timezone.localtime(), end=timezone.localtime(),
) )
self.assertEqual(tummy_time, models.TummyTime.objects.first()) self.assertEqual(tummy_time, models.TummyTime.objects.first())
self.assertEqual(str(tummy_time), 'Tummy Time') self.assertEqual(str(tummy_time), "Tummy Time")
self.assertEqual( self.assertEqual(tummy_time.duration, tummy_time.end - tummy_time.start)
tummy_time.duration, tummy_time.end - tummy_time.start)

View File

@ -16,113 +16,119 @@ class TemplateTagsTestCase(TestCase):
def test_bootstrap_bool_icon(self): def test_bootstrap_bool_icon(self):
self.assertEqual( self.assertEqual(
bootstrap.bool_icon(True), bootstrap.bool_icon(True),
'<i class="icon-true text-success" aria-hidden="true"></i>') '<i class="icon-true text-success" aria-hidden="true"></i>',
)
self.assertEqual( self.assertEqual(
bootstrap.bool_icon(False), bootstrap.bool_icon(False),
'<i class="icon-false text-danger" aria-hidden="true"></i>') '<i class="icon-false text-danger" aria-hidden="true"></i>',
)
def test_child_age_string(self): def test_child_age_string(self):
date = timezone.localdate() - timezone.timedelta(days=0, hours=6) date = timezone.localdate() - timezone.timedelta(days=0, hours=6)
self.assertEqual('0 days', duration.child_age_string(date)) self.assertEqual("0 days", duration.child_age_string(date))
date = timezone.localdate() - timezone.timedelta(days=1, hours=6) date = timezone.localdate() - timezone.timedelta(days=1, hours=6)
self.assertEqual('1\xa0day', duration.child_age_string(date)) self.assertEqual("1\xa0day", duration.child_age_string(date))
date = timezone.localdate() - timezone.timedelta(days=45) date = timezone.localdate() - timezone.timedelta(days=45)
self.assertEqual('1\xa0month', duration.child_age_string(date)) self.assertEqual("1\xa0month", duration.child_age_string(date))
date = timezone.localdate() - timezone.timedelta(days=95) date = timezone.localdate() - timezone.timedelta(days=95)
self.assertEqual('3\xa0months', duration.child_age_string(date)) self.assertEqual("3\xa0months", duration.child_age_string(date))
def test_duration_duration_string(self): def test_duration_duration_string(self):
delta = timezone.timedelta(hours=1, minutes=30, seconds=15) delta = timezone.timedelta(hours=1, minutes=30, seconds=15)
self.assertEqual( self.assertEqual(
duration.duration_string(delta), duration.duration_string(delta), "1 hour, 30 minutes, 15 seconds"
'1 hour, 30 minutes, 15 seconds') )
self.assertEqual( self.assertEqual(duration.duration_string(delta, "m"), "1 hour, 30 minutes")
duration.duration_string(delta, 'm'), self.assertEqual(duration.duration_string(delta, "h"), "1 hour")
'1 hour, 30 minutes')
self.assertEqual(duration.duration_string(delta, 'h'), '1 hour')
self.assertEqual(duration.duration_string(''), '') self.assertEqual(duration.duration_string(""), "")
self.assertRaises(TypeError, duration.duration_string('not a delta')) self.assertRaises(TypeError, duration.duration_string("not a delta"))
def test_duration_hours(self): def test_duration_hours(self):
delta = timezone.timedelta(hours=1) delta = timezone.timedelta(hours=1)
self.assertEqual(duration.hours(delta), 1) self.assertEqual(duration.hours(delta), 1)
self.assertEqual(duration.hours(''), 0) self.assertEqual(duration.hours(""), 0)
self.assertRaises(TypeError, duration.hours('not a delta')) self.assertRaises(TypeError, duration.hours("not a delta"))
def test_duration_minutes(self): def test_duration_minutes(self):
delta = timezone.timedelta(minutes=45) delta = timezone.timedelta(minutes=45)
self.assertEqual(duration.minutes(delta), 45) self.assertEqual(duration.minutes(delta), 45)
self.assertEqual(duration.minutes(''), 0) self.assertEqual(duration.minutes(""), 0)
self.assertRaises(TypeError, duration.minutes('not a delta')) self.assertRaises(TypeError, duration.minutes("not a delta"))
def test_duration_seconds(self): def test_duration_seconds(self):
delta = timezone.timedelta(seconds=20) delta = timezone.timedelta(seconds=20)
self.assertEqual(duration.seconds(delta), 20) self.assertEqual(duration.seconds(delta), 20)
self.assertEqual(duration.seconds(''), 0) self.assertEqual(duration.seconds(""), 0)
self.assertRaises(TypeError, duration.seconds('not a delta')) self.assertRaises(TypeError, duration.seconds("not a delta"))
def test_instance_add_url(self): def test_instance_add_url(self):
child = Child.objects.create(first_name='Test', last_name='Child', child = Child.objects.create(
birth_date=timezone.localdate()) first_name="Test", last_name="Child", birth_date=timezone.localdate()
user = User.objects.create_user(username='timer') )
user = User.objects.create_user(username="timer")
timer = Timer.objects.create(user=user) timer = Timer.objects.create(user=user)
url = timers.instance_add_url({'timer': timer}, 'core:sleep-add') url = timers.instance_add_url({"timer": timer}, "core:sleep-add")
self.assertEqual(url, '/sleep/add/?timer={}'.format(timer.id)) self.assertEqual(url, "/sleep/add/?timer={}".format(timer.id))
timer = Timer.objects.create(user=user, child=child) timer = Timer.objects.create(user=user, child=child)
url = timers.instance_add_url({'timer': timer}, 'core:sleep-add') url = timers.instance_add_url({"timer": timer}, "core:sleep-add")
self.assertEqual(url, '/sleep/add/?timer={}&child={}'.format( self.assertEqual(
timer.id, child.slug)) url, "/sleep/add/?timer={}&child={}".format(timer.id, child.slug)
)
def test_datetimepicker_format(self): def test_datetimepicker_format(self):
request = MockUserRequest(User.objects.first()) request = MockUserRequest(User.objects.first())
request.user.settings.dashboard_hide_empty = True request.user.settings.dashboard_hide_empty = True
context = {'request': request} context = {"request": request}
with self.settings(USE_24_HOUR_TIME_FORMAT=False): with self.settings(USE_24_HOUR_TIME_FORMAT=False):
self.assertEqual(datetime.datetimepicker_format(context), 'L LT') self.assertEqual(datetime.datetimepicker_format(context), "L LT")
self.assertEqual( self.assertEqual(datetime.datetimepicker_format(context, "L LT"), "L LT")
datetime.datetimepicker_format(context, 'L LT'), 'L LT') self.assertEqual(datetime.datetimepicker_format(context, "L LTS"), "L LTS")
self.assertEqual(
datetime.datetimepicker_format(context, 'L LTS'), 'L LTS')
with self.settings(USE_24_HOUR_TIME_FORMAT=True): with self.settings(USE_24_HOUR_TIME_FORMAT=True):
self.assertEqual(datetime.datetimepicker_format(context), "L HH:mm")
self.assertEqual(datetime.datetimepicker_format(context, "L LT"), "L HH:mm")
self.assertEqual( self.assertEqual(
datetime.datetimepicker_format(context), 'L HH:mm') datetime.datetimepicker_format(context, "L LTS"), "L HH:mm:ss"
self.assertEqual( )
datetime.datetimepicker_format(context, 'L LT'), 'L HH:mm')
self.assertEqual(
datetime.datetimepicker_format(context, 'L LTS'), 'L HH:mm:ss')
@override_settings(USE_24_HOUR_TIME_FORMAT=False) @override_settings(USE_24_HOUR_TIME_FORMAT=False)
def test_datetimepicker_format_en_gb(self): def test_datetimepicker_format_en_gb(self):
user = User.objects.first() user = User.objects.first()
user.settings.language = 'en-GB' user.settings.language = "en-GB"
user.save() user.save()
request = MockUserRequest(user) request = MockUserRequest(user)
request.user.settings.dashboard_hide_empty = True request.user.settings.dashboard_hide_empty = True
context = {'request': request} context = {"request": request}
self.assertEqual(datetime.datetimepicker_format(context), "L h:mm a")
self.assertEqual(datetime.datetimepicker_format(context, "L LT"), "L h:mm a")
self.assertEqual( self.assertEqual(
datetime.datetimepicker_format(context), 'L h:mm a') datetime.datetimepicker_format(context, "L LTS"), "L h:mm:ss a"
self.assertEqual( )
datetime.datetimepicker_format(context, 'L LT'), 'L h:mm a')
self.assertEqual(
datetime.datetimepicker_format(context, 'L LTS'), 'L h:mm:ss a')
def test_datetime_short(self): def test_datetime_short(self):
date = timezone.localtime() date = timezone.localtime()
self.assertEqual(datetime.datetime_short(date), "Today, {}".format( self.assertEqual(
formats.date_format(date, format='TIME_FORMAT'))) datetime.datetime_short(date),
"Today, {}".format(formats.date_format(date, format="TIME_FORMAT")),
)
date = timezone.localtime() - timezone.timedelta(days=1, hours=6) date = timezone.localtime() - timezone.timedelta(days=1, hours=6)
self.assertEqual(datetime.datetime_short(date), "{}, {}".format( self.assertEqual(
formats.date_format(date, format='SHORT_MONTH_DAY_FORMAT'), datetime.datetime_short(date),
formats.date_format(date, format='TIME_FORMAT'))) "{}, {}".format(
formats.date_format(date, format="SHORT_MONTH_DAY_FORMAT"),
formats.date_format(date, format="TIME_FORMAT"),
),
)
date = timezone.localtime() - timezone.timedelta(days=500) date = timezone.localtime() - timezone.timedelta(days=500)
self.assertEqual(datetime.datetime_short(date), formats.date_format( self.assertEqual(
date, format='SHORT_DATETIME_FORMAT')) datetime.datetime_short(date),
formats.date_format(date, format="SHORT_DATETIME_FORMAT"),
)

View File

@ -8,14 +8,12 @@ from core.utils import duration_string, duration_parts
class UtilsTestCase(TestCase): class UtilsTestCase(TestCase):
def test_duration_string(self): def test_duration_string(self):
duration = timezone.timedelta(hours=1, minutes=30, seconds=45) duration = timezone.timedelta(hours=1, minutes=30, seconds=45)
self.assertEqual( self.assertEqual(duration_string(duration), "1 hour, 30 minutes, 45 seconds")
duration_string(duration), self.assertEqual(duration_string(duration, "m"), "1 hour, 30 minutes")
'1 hour, 30 minutes, 45 seconds') self.assertEqual(duration_string(duration, "h"), "1 hour")
self.assertEqual(duration_string(duration, 'm'), '1 hour, 30 minutes') self.assertRaises(TypeError, lambda: duration_string("1 hour"))
self.assertEqual(duration_string(duration, 'h'), '1 hour')
self.assertRaises(TypeError, lambda: duration_string('1 hour'))
def test_duration_parts(self): def test_duration_parts(self):
duration = timezone.timedelta(hours=1, minutes=30, seconds=45) duration = timezone.timedelta(hours=1, minutes=30, seconds=45)
self.assertEqual(duration_parts(duration), (1, 30, 45)) self.assertEqual(duration_parts(duration), (1, 30, 45))
self.assertRaises(TypeError, lambda: duration_parts('1 hour')) self.assertRaises(TypeError, lambda: duration_parts("1 hour"))

View File

@ -15,174 +15,172 @@ class ViewsTestCase(TestCase):
def setUpClass(cls): def setUpClass(cls):
super(ViewsTestCase, cls).setUpClass() super(ViewsTestCase, cls).setUpClass()
fake = Factory.create() fake = Factory.create()
call_command('migrate', verbosity=0) call_command("migrate", verbosity=0)
call_command('fake', verbosity=0) call_command("fake", verbosity=0)
cls.c = HttpClient() cls.c = HttpClient()
fake_user = fake.simple_profile() fake_user = fake.simple_profile()
cls.credentials = { cls.credentials = {
'username': fake_user['username'], "username": fake_user["username"],
'password': fake.password() "password": fake.password(),
} }
cls.user = User.objects.create_user( cls.user = User.objects.create_user(is_superuser=True, **cls.credentials)
is_superuser=True, **cls.credentials)
cls.c.login(**cls.credentials) cls.c.login(**cls.credentials)
def test_child_views(self): def test_child_views(self):
page = self.c.get('/children/') page = self.c.get("/children/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/children/add/') page = self.c.get("/children/add/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
entry = models.Child.objects.first() entry = models.Child.objects.first()
page = self.c.get('/children/{}/'.format(entry.slug)) page = self.c.get("/children/{}/".format(entry.slug))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get( page = self.c.get(
'/children/{}/'.format(entry.slug), "/children/{}/".format(entry.slug),
{'date': timezone.localdate() - timezone.timedelta(days=1)}) {"date": timezone.localdate() - timezone.timedelta(days=1)},
)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/children/{}/edit/'.format(entry.slug)) page = self.c.get("/children/{}/edit/".format(entry.slug))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/children/{}/delete/'.format(entry.slug)) page = self.c.get("/children/{}/delete/".format(entry.slug))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
def test_diaperchange_views(self): def test_diaperchange_views(self):
page = self.c.get('/changes/') page = self.c.get("/changes/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/changes/add/') page = self.c.get("/changes/add/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
entry = models.DiaperChange.objects.first() entry = models.DiaperChange.objects.first()
page = self.c.get('/changes/{}/'.format(entry.id)) page = self.c.get("/changes/{}/".format(entry.id))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/changes/{}/delete/'.format(entry.id)) page = self.c.get("/changes/{}/delete/".format(entry.id))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
def test_feeding_views(self): def test_feeding_views(self):
page = self.c.get('/feedings/') page = self.c.get("/feedings/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/feedings/add/') page = self.c.get("/feedings/add/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
entry = models.Feeding.objects.first() entry = models.Feeding.objects.first()
page = self.c.get('/feedings/{}/'.format(entry.id)) page = self.c.get("/feedings/{}/".format(entry.id))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/feedings/{}/delete/'.format(entry.id)) page = self.c.get("/feedings/{}/delete/".format(entry.id))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
def test_note_views(self): def test_note_views(self):
page = self.c.get('/notes/') page = self.c.get("/notes/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/notes/add/') page = self.c.get("/notes/add/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
entry = models.Note.objects.first() entry = models.Note.objects.first()
page = self.c.get('/notes/{}/'.format(entry.id)) page = self.c.get("/notes/{}/".format(entry.id))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/notes/{}/delete/'.format(entry.id)) page = self.c.get("/notes/{}/delete/".format(entry.id))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
def test_sleep_views(self): def test_sleep_views(self):
page = self.c.get('/sleep/') page = self.c.get("/sleep/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/sleep/add/') page = self.c.get("/sleep/add/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
entry = models.Sleep.objects.first() entry = models.Sleep.objects.first()
page = self.c.get('/sleep/{}/'.format(entry.id)) page = self.c.get("/sleep/{}/".format(entry.id))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/sleep/{}/delete/'.format(entry.id)) page = self.c.get("/sleep/{}/delete/".format(entry.id))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
def test_temperature_views(self): def test_temperature_views(self):
page = self.c.get('/temperature/') page = self.c.get("/temperature/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/temperature/add/') page = self.c.get("/temperature/add/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
entry = models.Temperature.objects.first() entry = models.Temperature.objects.first()
page = self.c.get('/temperature/{}/'.format(entry.id)) page = self.c.get("/temperature/{}/".format(entry.id))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/temperature/{}/delete/'.format(entry.id)) page = self.c.get("/temperature/{}/delete/".format(entry.id))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
def test_timer_views(self): def test_timer_views(self):
page = self.c.get('/timers/') page = self.c.get("/timers/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/timers/add/') page = self.c.get("/timers/add/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/timers/add/quick/') page = self.c.get("/timers/add/quick/")
self.assertEqual(page.status_code, 405) self.assertEqual(page.status_code, 405)
page = self.c.post('/timers/add/quick/', follow=True) page = self.c.post("/timers/add/quick/", follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
entry = models.Timer.objects.first() entry = models.Timer.objects.first()
page = self.c.get('/timers/{}/'.format(entry.id)) page = self.c.get("/timers/{}/".format(entry.id))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/timers/{}/edit/'.format(entry.id)) page = self.c.get("/timers/{}/edit/".format(entry.id))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/timers/{}/delete/'.format(entry.id)) page = self.c.get("/timers/{}/delete/".format(entry.id))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/timers/{}/stop/'.format(entry.id)) page = self.c.get("/timers/{}/stop/".format(entry.id))
self.assertEqual(page.status_code, 405) self.assertEqual(page.status_code, 405)
page = self.c.post('/timers/{}/stop/'.format(entry.id), follow=True) page = self.c.post("/timers/{}/stop/".format(entry.id), follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/timers/{}/restart/'.format(entry.id)) page = self.c.get("/timers/{}/restart/".format(entry.id))
self.assertEqual(page.status_code, 405) self.assertEqual(page.status_code, 405)
page = self.c.post('/timers/{}/restart/'.format(entry.id), follow=True) page = self.c.post("/timers/{}/restart/".format(entry.id), follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/timers/delete-inactive/', follow=True) page = self.c.get("/timers/delete-inactive/", follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
messages = list(page.context['messages']) messages = list(page.context["messages"])
self.assertEqual(len(messages), 1) self.assertEqual(len(messages), 1)
self.assertEqual(str(messages[0]), 'No inactive timers exist.') self.assertEqual(str(messages[0]), "No inactive timers exist.")
entry = models.Timer.objects.first() entry = models.Timer.objects.first()
entry.stop() entry.stop()
page = self.c.get('/timers/delete-inactive/') page = self.c.get("/timers/delete-inactive/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertEqual(page.context['timer_count'], 1) self.assertEqual(page.context["timer_count"], 1)
def test_timeline_views(self): def test_timeline_views(self):
child = models.Child.objects.first() child = models.Child.objects.first()
response = self.c.get('/timeline/') response = self.c.get("/timeline/")
self.assertRedirects(response, '/children/{}/'.format(child.slug)) self.assertRedirects(response, "/children/{}/".format(child.slug))
models.Child.objects.create( models.Child.objects.create(
first_name='Second', first_name="Second", last_name="Child", birth_date="2000-01-01"
last_name='Child',
birth_date='2000-01-01'
) )
response = self.c.get('/timeline/') response = self.c.get("/timeline/")
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_tummytime_views(self): def test_tummytime_views(self):
page = self.c.get('/tummy-time/') page = self.c.get("/tummy-time/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/tummy-time/add/') page = self.c.get("/tummy-time/add/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
entry = models.TummyTime.objects.first() entry = models.TummyTime.objects.first()
page = self.c.get('/tummy-time/{}/'.format(entry.id)) page = self.c.get("/tummy-time/{}/".format(entry.id))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/tummy-time/{}/delete/'.format(entry.id)) page = self.c.get("/tummy-time/{}/delete/".format(entry.id))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
def test_weight_views(self): def test_weight_views(self):
page = self.c.get('/weight/') page = self.c.get("/weight/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/weight/add/') page = self.c.get("/weight/add/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
entry = models.Weight.objects.first() entry = models.Weight.objects.first()
page = self.c.get('/weight/{}/'.format(entry.id)) page = self.c.get("/weight/{}/".format(entry.id))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get('/weight/{}/delete/'.format(entry.id)) page = self.c.get("/weight/{}/delete/".format(entry.id))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)

View File

@ -24,11 +24,11 @@ def get_objects(date, child=None):
_add_tummy_times(min_date, max_date, events, child) _add_tummy_times(min_date, max_date, events, child)
_add_notes(min_date, max_date, events, child) _add_notes(min_date, max_date, events, child)
explicit_type_ordering = {'start': 0, 'end': 1} explicit_type_ordering = {"start": 0, "end": 1}
events.sort( events.sort(
key=lambda x: ( key=lambda x: (
x['time'], x["time"],
explicit_type_ordering.get(x.get('type'), -1), explicit_type_ordering.get(x.get("type"), -1),
), ),
reverse=True, reverse=True,
) )
@ -37,69 +37,74 @@ def get_objects(date, child=None):
def _add_tummy_times(min_date, max_date, events, child=None): def _add_tummy_times(min_date, max_date, events, child=None):
instances = TummyTime.objects.filter( instances = TummyTime.objects.filter(start__range=(min_date, max_date)).order_by(
start__range=(min_date, max_date)).order_by('-start') "-start"
)
if child: if child:
instances = instances.filter(child=child) instances = instances.filter(child=child)
for instance in instances: for instance in instances:
details = [] details = []
if instance.milestone: if instance.milestone:
details.append(instance.milestone) details.append(instance.milestone)
edit_link = reverse('core:tummytime-update', args=[instance.id]) edit_link = reverse("core:tummytime-update", args=[instance.id])
events.append({ events.append(
'time': timezone.localtime(instance.start), {
'event': _('%(child)s started tummy time!') % { "time": timezone.localtime(instance.start),
'child': instance.child.first_name "event": _("%(child)s started tummy time!")
}, % {"child": instance.child.first_name},
'details': details, "details": details,
'edit_link': edit_link, "edit_link": edit_link,
'model_name': instance.model_name, "model_name": instance.model_name,
'type': 'start' "type": "start",
}) }
events.append({ )
'time': timezone.localtime(instance.end), events.append(
'event': _('%(child)s finished tummy time.') % { {
'child': instance.child.first_name "time": timezone.localtime(instance.end),
}, "event": _("%(child)s finished tummy time.")
'details': details, % {"child": instance.child.first_name},
'edit_link': edit_link, "details": details,
'duration': timesince.timesince(instance.start, now=instance.end), "edit_link": edit_link,
'model_name': instance.model_name, "duration": timesince.timesince(instance.start, now=instance.end),
'type': 'end' "model_name": instance.model_name,
}) "type": "end",
}
)
def _add_sleeps(min_date, max_date, events, child=None): def _add_sleeps(min_date, max_date, events, child=None):
instances = Sleep.objects.filter( instances = Sleep.objects.filter(start__range=(min_date, max_date)).order_by(
start__range=(min_date, max_date)).order_by('-start') "-start"
)
if child: if child:
instances = instances.filter(child=child) instances = instances.filter(child=child)
for instance in instances: for instance in instances:
details = [] details = []
if instance.notes: if instance.notes:
details.append(instance.notes) details.append(instance.notes)
edit_link = reverse('core:sleep-update', args=[instance.id]) edit_link = reverse("core:sleep-update", args=[instance.id])
events.append({ events.append(
'time': timezone.localtime(instance.start), {
'event': _('%(child)s fell asleep.') % { "time": timezone.localtime(instance.start),
'child': instance.child.first_name "event": _("%(child)s fell asleep.")
}, % {"child": instance.child.first_name},
'details': details, "details": details,
'edit_link': edit_link, "edit_link": edit_link,
'model_name': instance.model_name, "model_name": instance.model_name,
'type': 'start' "type": "start",
}) }
events.append({ )
'time': timezone.localtime(instance.end), events.append(
'event': _('%(child)s woke up.') % { {
'child': instance.child.first_name "time": timezone.localtime(instance.end),
}, "event": _("%(child)s woke up.") % {"child": instance.child.first_name},
'details': details, "details": details,
'edit_link': edit_link, "edit_link": edit_link,
'duration': timesince.timesince(instance.start, now=instance.end), "duration": timesince.timesince(instance.start, now=instance.end),
'model_name': instance.model_name, "model_name": instance.model_name,
'type': 'end' "type": "end",
}) }
)
def _add_feedings(min_date, max_date, events, child=None): def _add_feedings(min_date, max_date, events, child=None):
@ -107,8 +112,9 @@ def _add_feedings(min_date, max_date, events, child=None):
yesterday = min_date - timedelta(days=1) yesterday = min_date - timedelta(days=1)
prev_start = None prev_start = None
instances = Feeding.objects.filter( instances = Feeding.objects.filter(start__range=(yesterday, max_date)).order_by(
start__range=(yesterday, max_date)).order_by('start') "start"
)
if child: if child:
instances = instances.filter(child=child) instances = instances.filter(child=child)
for instance in instances: for instance in instances:
@ -117,73 +123,80 @@ def _add_feedings(min_date, max_date, events, child=None):
details.append(instance.notes) details.append(instance.notes)
time_since_prev = None time_since_prev = None
if prev_start: if prev_start:
time_since_prev = \ time_since_prev = timesince.timesince(prev_start, now=instance.start)
timesince.timesince(prev_start, now=instance.start)
prev_start = instance.start prev_start = instance.start
if instance.start < min_date: if instance.start < min_date:
continue continue
edit_link = reverse('core:feeding-update', args=[instance.id]) edit_link = reverse("core:feeding-update", args=[instance.id])
if instance.amount: if instance.amount:
details.append(_('Amount: %(amount).0f') % { details.append(
'amount': instance.amount, _("Amount: %(amount).0f")
}) % {
events.append({ "amount": instance.amount,
'time': timezone.localtime(instance.start), }
'event': _('%(child)s started feeding.') % { )
'child': instance.child.first_name events.append(
}, {
'details': details, "time": timezone.localtime(instance.start),
'edit_link': edit_link, "event": _("%(child)s started feeding.")
'time_since_prev': time_since_prev, % {"child": instance.child.first_name},
'model_name': instance.model_name, "details": details,
'type': 'start' "edit_link": edit_link,
}) "time_since_prev": time_since_prev,
events.append({ "model_name": instance.model_name,
'time': timezone.localtime(instance.end), "type": "start",
'event': _('%(child)s finished feeding.') % { }
'child': instance.child.first_name )
}, events.append(
'details': details, {
'edit_link': edit_link, "time": timezone.localtime(instance.end),
'duration': timesince.timesince(instance.start, now=instance.end), "event": _("%(child)s finished feeding.")
'model_name': instance.model_name, % {"child": instance.child.first_name},
'type': 'end' "details": details,
}) "edit_link": edit_link,
"duration": timesince.timesince(instance.start, now=instance.end),
"model_name": instance.model_name,
"type": "end",
}
)
def _add_diaper_changes(min_date, max_date, events, child): def _add_diaper_changes(min_date, max_date, events, child):
instances = DiaperChange.objects.filter( instances = DiaperChange.objects.filter(time__range=(min_date, max_date)).order_by(
time__range=(min_date, max_date)).order_by('-time') "-time"
)
if child: if child:
instances = instances.filter(child=child) instances = instances.filter(child=child)
for instance in instances: for instance in instances:
contents = [] contents = []
if instance.wet: if instance.wet:
contents.append('💧') contents.append("💧")
if instance.solid: if instance.solid:
contents.append('💩') contents.append("💩")
events.append({ events.append(
'time': timezone.localtime(instance.time), {
'event': _('%(child)s had a %(type)s diaper change.') % { "time": timezone.localtime(instance.time),
'child': instance.child.first_name, "event": _("%(child)s had a %(type)s diaper change.")
'type': ''.join(contents), % {
"child": instance.child.first_name,
"type": "".join(contents),
}, },
'edit_link': reverse('core:diaperchange-update', "edit_link": reverse("core:diaperchange-update", args=[instance.id]),
args=[instance.id]), "model_name": instance.model_name,
'model_name': instance.model_name }
}) )
def _add_notes(min_date, max_date, events, child): def _add_notes(min_date, max_date, events, child):
instances = Note.objects.filter( instances = Note.objects.filter(time__range=(min_date, max_date)).order_by("-time")
time__range=(min_date, max_date)).order_by('-time')
if child: if child:
instances = instances.filter(child=child) instances = instances.filter(child=child)
for instance in instances: for instance in instances:
events.append({ events.append(
'time': timezone.localtime(instance.time), {
'details': [instance.note], "time": timezone.localtime(instance.time),
'edit_link': reverse('core:note-update', "details": [instance.note],
args=[instance.id]), "edit_link": reverse("core:note-update", args=[instance.id]),
'model_name': instance.model_name "model_name": instance.model_name,
}) }
)

View File

@ -3,206 +3,112 @@ from django.urls import path
from . import views from . import views
app_name = 'core' app_name = "core"
urlpatterns = [ urlpatterns = [
path('children/', views.ChildList.as_view(), name='child-list'), path("children/", views.ChildList.as_view(), name="child-list"),
path('children/add/', views.ChildAdd.as_view(), name='child-add'), path("children/add/", views.ChildAdd.as_view(), name="child-add"),
path('children/<str:slug>/', views.ChildDetail.as_view(), name='child'), path("children/<str:slug>/", views.ChildDetail.as_view(), name="child"),
path("children/<str:slug>/edit/", views.ChildUpdate.as_view(), name="child-update"),
path( path(
'children/<str:slug>/edit/', "children/<str:slug>/delete/", views.ChildDelete.as_view(), name="child-delete"
views.ChildUpdate.as_view(),
name='child-update'
), ),
path("timeline/", views.Timeline.as_view(), name="timeline"),
path("changes/", views.DiaperChangeList.as_view(), name="diaperchange-list"),
path("changes/add/", views.DiaperChangeAdd.as_view(), name="diaperchange-add"),
path( path(
'children/<str:slug>/delete/', "changes/<int:pk>/",
views.ChildDelete.as_view(),
name='child-delete'
),
path('timeline/', views.Timeline.as_view(), name='timeline'),
path(
'changes/',
views.DiaperChangeList.as_view(),
name='diaperchange-list'
),
path(
'changes/add/',
views.DiaperChangeAdd.as_view(),
name='diaperchange-add'
),
path(
'changes/<int:pk>/',
views.DiaperChangeUpdate.as_view(), views.DiaperChangeUpdate.as_view(),
name='diaperchange-update' name="diaperchange-update",
), ),
path( path(
'changes/<int:pk>/delete/', "changes/<int:pk>/delete/",
views.DiaperChangeDelete.as_view(), views.DiaperChangeDelete.as_view(),
name='diaperchange-delete' name="diaperchange-delete",
), ),
path("feedings/", views.FeedingList.as_view(), name="feeding-list"),
path('feedings/', views.FeedingList.as_view(), name='feeding-list'), path("feedings/add/", views.FeedingAdd.as_view(), name="feeding-add"),
path('feedings/add/', views.FeedingAdd.as_view(), name='feeding-add'), path("feedings/<int:pk>/", views.FeedingUpdate.as_view(), name="feeding-update"),
path( path(
'feedings/<int:pk>/', "feedings/<int:pk>/delete/",
views.FeedingUpdate.as_view(),
name='feeding-update'
),
path(
'feedings/<int:pk>/delete/',
views.FeedingDelete.as_view(), views.FeedingDelete.as_view(),
name='feeding-delete' name="feeding-delete",
), ),
path("notes/", views.NoteList.as_view(), name="note-list"),
path('notes/', views.NoteList.as_view(), name='note-list'), path("notes/add/", views.NoteAdd.as_view(), name="note-add"),
path('notes/add/', views.NoteAdd.as_view(), name='note-add'), path("notes/<int:pk>/", views.NoteUpdate.as_view(), name="note-update"),
path('notes/<int:pk>/', views.NoteUpdate.as_view(), name='note-update'), path("notes/<int:pk>/delete/", views.NoteDelete.as_view(), name="note-delete"),
path("sleep/", views.SleepList.as_view(), name="sleep-list"),
path("sleep/add/", views.SleepAdd.as_view(), name="sleep-add"),
path("sleep/<int:pk>/", views.SleepUpdate.as_view(), name="sleep-update"),
path("sleep/<int:pk>/delete/", views.SleepDelete.as_view(), name="sleep-delete"),
path("temperature/", views.TemperatureList.as_view(), name="temperature-list"),
path("temperature/add/", views.TemperatureAdd.as_view(), name="temperature-add"),
path( path(
'notes/<int:pk>/delete/', "temperature/<int:pk>/",
views.NoteDelete.as_view(),
name='note-delete'
),
path('sleep/', views.SleepList.as_view(), name='sleep-list'),
path('sleep/add/', views.SleepAdd.as_view(), name='sleep-add'),
path('sleep/<int:pk>/', views.SleepUpdate.as_view(), name='sleep-update'),
path(
'sleep/<int:pk>/delete/',
views.SleepDelete.as_view(),
name='sleep-delete'
),
path(
'temperature/',
views.TemperatureList.as_view(),
name='temperature-list'
),
path(
'temperature/add/',
views.TemperatureAdd.as_view(),
name='temperature-add'
),
path(
'temperature/<int:pk>/',
views.TemperatureUpdate.as_view(), views.TemperatureUpdate.as_view(),
name='temperature-update' name="temperature-update",
), ),
path( path(
'temperature/<int:pk>/delete/', "temperature/<int:pk>/delete/",
views.TemperatureDelete.as_view(), views.TemperatureDelete.as_view(),
name='temperature-delete' name="temperature-delete",
), ),
path("timers/", views.TimerList.as_view(), name="timer-list"),
path('timers/', views.TimerList.as_view(), name='timer-list'), path("timers/add/", views.TimerAdd.as_view(), name="timer-add"),
path('timers/add/', views.TimerAdd.as_view(), name='timer-add'), path("timers/add/quick/", views.TimerAddQuick.as_view(), name="timer-add-quick"),
path("timers/<int:pk>/", views.TimerDetail.as_view(), name="timer-detail"),
path("timers/<int:pk>/edit/", views.TimerUpdate.as_view(), name="timer-update"),
path("timers/<int:pk>/delete/", views.TimerDelete.as_view(), name="timer-delete"),
path("timers/<int:pk>/stop/", views.TimerStop.as_view(), name="timer-stop"),
path( path(
'timers/add/quick/', "timers/<int:pk>/restart/", views.TimerRestart.as_view(), name="timer-restart"
views.TimerAddQuick.as_view(),
name='timer-add-quick'
),
path('timers/<int:pk>/', views.TimerDetail.as_view(), name='timer-detail'),
path(
'timers/<int:pk>/edit/',
views.TimerUpdate.as_view(),
name='timer-update'
), ),
path( path(
'timers/<int:pk>/delete/', "timers/delete-inactive/",
views.TimerDelete.as_view(),
name='timer-delete'
),
path(
'timers/<int:pk>/stop/',
views.TimerStop.as_view(),
name='timer-stop'
),
path(
'timers/<int:pk>/restart/',
views.TimerRestart.as_view(),
name='timer-restart'
),
path(
'timers/delete-inactive/',
views.TimerDeleteInactive.as_view(), views.TimerDeleteInactive.as_view(),
name='timer-delete-inactive' name="timer-delete-inactive",
), ),
path("tummy-time/", views.TummyTimeList.as_view(), name="tummytime-list"),
path('tummy-time/', views.TummyTimeList.as_view(), name='tummytime-list'), path("tummy-time/add/", views.TummyTimeAdd.as_view(), name="tummytime-add"),
path( path(
'tummy-time/add/', "tummy-time/<int:pk>/", views.TummyTimeUpdate.as_view(), name="tummytime-update"
views.TummyTimeAdd.as_view(),
name='tummytime-add'
), ),
path( path(
'tummy-time/<int:pk>/', "tummy-time/<int:pk>/delete/",
views.TummyTimeUpdate.as_view(),
name='tummytime-update'
),
path(
'tummy-time/<int:pk>/delete/',
views.TummyTimeDelete.as_view(), views.TummyTimeDelete.as_view(),
name='tummytime-delete' name="tummytime-delete",
), ),
path("weight/", views.WeightList.as_view(), name="weight-list"),
path('weight/', views.WeightList.as_view(), name='weight-list'), path("weight/add/", views.WeightAdd.as_view(), name="weight-add"),
path('weight/add/', views.WeightAdd.as_view(), name='weight-add'), path("weight/<int:pk>/", views.WeightUpdate.as_view(), name="weight-update"),
path("weight/<int:pk>/delete/", views.WeightDelete.as_view(), name="weight-delete"),
path("height/", views.HeightList.as_view(), name="height-list"),
path("height/add/", views.HeightAdd.as_view(), name="height-add"),
path("height/<int:pk>/", views.HeightUpdate.as_view(), name="height-update"),
path("height/<int:pk>/delete/", views.HeightDelete.as_view(), name="height-delete"),
path( path(
'weight/<int:pk>/', "head-circumference/",
views.WeightUpdate.as_view(),
name='weight-update'
),
path(
'weight/<int:pk>/delete/',
views.WeightDelete.as_view(),
name='weight-delete'
),
path('height/', views.HeightList.as_view(), name='height-list'),
path('height/add/', views.HeightAdd.as_view(), name='height-add'),
path(
'height/<int:pk>/',
views.HeightUpdate.as_view(),
name='height-update'
),
path(
'height/<int:pk>/delete/',
views.HeightDelete.as_view(),
name='height-delete'
),
path(
'head-circumference/',
views.HeadCircumferenceList.as_view(), views.HeadCircumferenceList.as_view(),
name='head-circumference-list' name="head-circumference-list",
), ),
path( path(
'head-circumference/add/', "head-circumference/add/",
views.HeadCircumferenceAdd.as_view(), views.HeadCircumferenceAdd.as_view(),
name='head-circumference-add' name="head-circumference-add",
), ),
path( path(
'head-circumference/<int:pk>/', "head-circumference/<int:pk>/",
views.HeadCircumferenceUpdate.as_view(), views.HeadCircumferenceUpdate.as_view(),
name='head-circumference-update' name="head-circumference-update",
), ),
path( path(
'head-circumference/<int:pk>/delete/', "head-circumference/<int:pk>/delete/",
views.HeadCircumferenceDelete.as_view(), views.HeadCircumferenceDelete.as_view(),
name='head-circumference-delete' name="head-circumference-delete",
),
path('bmi/', views.BMIList.as_view(), name='bmi-list'),
path('bmi/add/', views.BMIAdd.as_view(), name='bmi-add'),
path(
'bmi/<int:pk>/',
views.BMIUpdate.as_view(),
name='bmi-update'
),
path(
'bmi/<int:pk>/delete/',
views.BMIDelete.as_view(),
name='bmi-delete'
), ),
path("bmi/", views.BMIList.as_view(), name="bmi-list"),
path("bmi/add/", views.BMIAdd.as_view(), name="bmi-add"),
path("bmi/<int:pk>/", views.BMIUpdate.as_view(), name="bmi-update"),
path("bmi/<int:pk>/delete/", views.BMIDelete.as_view(), name="bmi-delete"),
] ]

View File

@ -3,43 +3,36 @@ from django.utils import timezone
from django.utils.translation import ngettext from django.utils.translation import ngettext
def duration_string(duration, precision='s'): def duration_string(duration, precision="s"):
"""Format hours, minutes and seconds as a human-friendly string (e.g. "2 """Format hours, minutes and seconds as a human-friendly string (e.g. "2
hours, 25 minutes, 31 seconds") with precision to h = hours, m = minutes or hours, 25 minutes, 31 seconds") with precision to h = hours, m = minutes or
s = seconds. s = seconds.
""" """
h, m, s = duration_parts(duration) h, m, s = duration_parts(duration)
duration = '' duration = ""
if h > 0: if h > 0:
duration = ngettext('%(hours)s hour', '%(hours)s hours', h) % { duration = ngettext("%(hours)s hour", "%(hours)s hours", h) % {"hours": h}
'hours': h if m > 0 and precision != "h":
if duration != "":
duration += ", "
duration += ngettext("%(minutes)s minute", "%(minutes)s minutes", m) % {
"minutes": m
}
if s > 0 and precision != "h" and precision != "m":
if duration != "":
duration += ", "
duration += ngettext("%(seconds)s second", "%(seconds)s seconds", s) % {
"seconds": s
} }
if m > 0 and precision != 'h':
if duration != '':
duration += ', '
duration += ngettext(
'%(minutes)s minute',
'%(minutes)s minutes',
m
) % {'minutes': m}
if s > 0 and precision != 'h' and precision != 'm':
if duration != '':
duration += ', '
duration += ngettext(
'%(seconds)s second',
'%(seconds)s seconds',
s
) % {'seconds': s}
return duration return duration
def duration_parts(duration): def duration_parts(duration):
"""Get hours, minutes and seconds from a timedelta. """Get hours, minutes and seconds from a timedelta."""
"""
if not isinstance(duration, timezone.timedelta): if not isinstance(duration, timezone.timedelta):
raise TypeError('Duration provided must be a timedetla') raise TypeError("Duration provided must be a timedetla")
h, remainder = divmod(duration.seconds, 3600) h, remainder = divmod(duration.seconds, 3600)
h += duration.days * 24 h += duration.days * 24
m, s = divmod(remainder, 60) m, s = divmod(remainder, 60)

View File

@ -8,8 +8,7 @@ from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic.base import RedirectView, TemplateView from django.views.generic.base import RedirectView, TemplateView
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView, \ from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView
FormView
from babybuddy.mixins import LoginRequiredMixin, PermissionRequiredMixin from babybuddy.mixins import LoginRequiredMixin, PermissionRequiredMixin
from babybuddy.views import BabyBuddyFilterView from babybuddy.views import BabyBuddyFilterView
@ -17,23 +16,23 @@ from core import forms, models, timeline
def _prepare_timeline_context_data(context, date, child=None): def _prepare_timeline_context_data(context, date, child=None):
date = timezone.datetime.strptime(date, '%Y-%m-%d') date = timezone.datetime.strptime(date, "%Y-%m-%d")
date = timezone.localtime(timezone.make_aware(date)) date = timezone.localtime(timezone.make_aware(date))
context['timeline_objects'] = timeline.get_objects(date, child) context["timeline_objects"] = timeline.get_objects(date, child)
context['date'] = date context["date"] = date
context['date_previous'] = date - timezone.timedelta(days=1) context["date_previous"] = date - timezone.timedelta(days=1)
if date.date() < timezone.localdate(): if date.date() < timezone.localdate():
context['date_next'] = date + timezone.timedelta(days=1) context["date_next"] = date + timezone.timedelta(days=1)
pass pass
class CoreAddView(PermissionRequiredMixin, SuccessMessageMixin, CreateView): class CoreAddView(PermissionRequiredMixin, SuccessMessageMixin, CreateView):
def get_success_message(self, cleaned_data): def get_success_message(self, cleaned_data):
cleaned_data['model'] = self.model._meta.verbose_name.title() cleaned_data["model"] = self.model._meta.verbose_name.title()
if 'child' in cleaned_data: if "child" in cleaned_data:
self.success_message = _('%(model)s entry for %(child)s added!') self.success_message = _("%(model)s entry for %(child)s added!")
else: else:
self.success_message = _('%(model)s entry added!') self.success_message = _("%(model)s entry added!")
return self.success_message % cleaned_data return self.success_message % cleaned_data
def get_form_kwargs(self): def get_form_kwargs(self):
@ -48,291 +47,287 @@ class CoreAddView(PermissionRequiredMixin, SuccessMessageMixin, CreateView):
:return: Updated keyword arguments. :return: Updated keyword arguments.
""" """
kwargs = super(CoreAddView, self).get_form_kwargs() kwargs = super(CoreAddView, self).get_form_kwargs()
for parameter in ['child', 'timer']: for parameter in ["child", "timer"]:
value = self.request.GET.get(parameter, None) value = self.request.GET.get(parameter, None)
if value: if value:
kwargs.update({parameter: value}) kwargs.update({parameter: value})
return kwargs return kwargs
class CoreUpdateView(PermissionRequiredMixin, SuccessMessageMixin, class CoreUpdateView(PermissionRequiredMixin, SuccessMessageMixin, UpdateView):
UpdateView):
def get_success_message(self, cleaned_data): def get_success_message(self, cleaned_data):
cleaned_data['model'] = self.model._meta.verbose_name.title() cleaned_data["model"] = self.model._meta.verbose_name.title()
if 'child' in cleaned_data: if "child" in cleaned_data:
self.success_message = _('%(model)s entry for %(child)s updated.') self.success_message = _("%(model)s entry for %(child)s updated.")
else: else:
self.success_message = _('%(model)s entry updated.') self.success_message = _("%(model)s entry updated.")
return self.success_message % cleaned_data return self.success_message % cleaned_data
class CoreDeleteView(PermissionRequiredMixin, SuccessMessageMixin, DeleteView): class CoreDeleteView(PermissionRequiredMixin, SuccessMessageMixin, DeleteView):
def get_success_message(self, cleaned_data): def get_success_message(self, cleaned_data):
return _('%(model)s entry deleted.') % { return _("%(model)s entry deleted.") % {
'model': self.model._meta.verbose_name.title() "model": self.model._meta.verbose_name.title()
} }
class ChildList(PermissionRequiredMixin, BabyBuddyFilterView): class ChildList(PermissionRequiredMixin, BabyBuddyFilterView):
model = models.Child model = models.Child
template_name = 'core/child_list.html' template_name = "core/child_list.html"
permission_required = ('core.view_child',) permission_required = ("core.view_child",)
paginate_by = 10 paginate_by = 10
filterset_fields = ('first_name', 'last_name') filterset_fields = ("first_name", "last_name")
class ChildAdd(CoreAddView): class ChildAdd(CoreAddView):
model = models.Child model = models.Child
permission_required = ('core.add_child',) permission_required = ("core.add_child",)
form_class = forms.ChildForm form_class = forms.ChildForm
success_url = reverse_lazy('core:child-list') success_url = reverse_lazy("core:child-list")
success_message = _('%(first_name)s %(last_name)s added!') success_message = _("%(first_name)s %(last_name)s added!")
class ChildDetail(PermissionRequiredMixin, DetailView): class ChildDetail(PermissionRequiredMixin, DetailView):
model = models.Child model = models.Child
permission_required = ('core.view_child',) permission_required = ("core.view_child",)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(ChildDetail, self).get_context_data(**kwargs) context = super(ChildDetail, self).get_context_data(**kwargs)
date = self.request.GET.get('date', str(timezone.localdate())) date = self.request.GET.get("date", str(timezone.localdate()))
_prepare_timeline_context_data(context, date, self.object) _prepare_timeline_context_data(context, date, self.object)
return context return context
class ChildUpdate(CoreUpdateView): class ChildUpdate(CoreUpdateView):
model = models.Child model = models.Child
permission_required = ('core.change_child',) permission_required = ("core.change_child",)
form_class = forms.ChildForm form_class = forms.ChildForm
success_url = reverse_lazy('core:child-list') success_url = reverse_lazy("core:child-list")
class ChildDelete(CoreUpdateView): class ChildDelete(CoreUpdateView):
model = models.Child model = models.Child
form_class = forms.ChildDeleteForm form_class = forms.ChildDeleteForm
template_name = 'core/child_confirm_delete.html' template_name = "core/child_confirm_delete.html"
permission_required = ('core.delete_child',) permission_required = ("core.delete_child",)
success_url = reverse_lazy('core:child-list') success_url = reverse_lazy("core:child-list")
def get_success_message(self, cleaned_data): def get_success_message(self, cleaned_data):
"""This class cannot use `CoreDeleteView` because of the confirmation """This class cannot use `CoreDeleteView` because of the confirmation
step required so the success message must be overridden.""" step required so the success message must be overridden."""
success_message = _('%(model)s entry deleted.') % { success_message = _("%(model)s entry deleted.") % {
'model': self.model._meta.verbose_name.title() "model": self.model._meta.verbose_name.title()
} }
return success_message % cleaned_data return success_message % cleaned_data
class DiaperChangeList(PermissionRequiredMixin, BabyBuddyFilterView): class DiaperChangeList(PermissionRequiredMixin, BabyBuddyFilterView):
model = models.DiaperChange model = models.DiaperChange
template_name = 'core/diaperchange_list.html' template_name = "core/diaperchange_list.html"
permission_required = ('core.view_diaperchange',) permission_required = ("core.view_diaperchange",)
paginate_by = 10 paginate_by = 10
filterset_fields = ('child', 'wet', 'solid', 'color') filterset_fields = ("child", "wet", "solid", "color")
class DiaperChangeAdd(CoreAddView): class DiaperChangeAdd(CoreAddView):
model = models.DiaperChange model = models.DiaperChange
permission_required = ('core.add_diaperchange',) permission_required = ("core.add_diaperchange",)
form_class = forms.DiaperChangeForm form_class = forms.DiaperChangeForm
success_url = reverse_lazy('core:diaperchange-list') success_url = reverse_lazy("core:diaperchange-list")
class DiaperChangeUpdate(CoreUpdateView): class DiaperChangeUpdate(CoreUpdateView):
model = models.DiaperChange model = models.DiaperChange
permission_required = ('core.change_diaperchange',) permission_required = ("core.change_diaperchange",)
form_class = forms.DiaperChangeForm form_class = forms.DiaperChangeForm
success_url = reverse_lazy('core:diaperchange-list') success_url = reverse_lazy("core:diaperchange-list")
class DiaperChangeDelete(CoreDeleteView): class DiaperChangeDelete(CoreDeleteView):
model = models.DiaperChange model = models.DiaperChange
permission_required = ('core.delete_diaperchange',) permission_required = ("core.delete_diaperchange",)
success_url = reverse_lazy('core:diaperchange-list') success_url = reverse_lazy("core:diaperchange-list")
class FeedingList(PermissionRequiredMixin, BabyBuddyFilterView): class FeedingList(PermissionRequiredMixin, BabyBuddyFilterView):
model = models.Feeding model = models.Feeding
template_name = 'core/feeding_list.html' template_name = "core/feeding_list.html"
permission_required = ('core.view_feeding',) permission_required = ("core.view_feeding",)
paginate_by = 10 paginate_by = 10
filterset_fields = ('child', 'type', 'method') filterset_fields = ("child", "type", "method")
class FeedingAdd(CoreAddView): class FeedingAdd(CoreAddView):
model = models.Feeding model = models.Feeding
permission_required = ('core.add_feeding',) permission_required = ("core.add_feeding",)
form_class = forms.FeedingForm form_class = forms.FeedingForm
success_url = reverse_lazy('core:feeding-list') success_url = reverse_lazy("core:feeding-list")
class FeedingUpdate(CoreUpdateView): class FeedingUpdate(CoreUpdateView):
model = models.Feeding model = models.Feeding
permission_required = ('core.change_feeding',) permission_required = ("core.change_feeding",)
form_class = forms.FeedingForm form_class = forms.FeedingForm
success_url = reverse_lazy('core:feeding-list') success_url = reverse_lazy("core:feeding-list")
class FeedingDelete(CoreDeleteView): class FeedingDelete(CoreDeleteView):
model = models.Feeding model = models.Feeding
permission_required = ('core.delete_feeding',) permission_required = ("core.delete_feeding",)
success_url = reverse_lazy('core:feeding-list') success_url = reverse_lazy("core:feeding-list")
class NoteList(PermissionRequiredMixin, BabyBuddyFilterView): class NoteList(PermissionRequiredMixin, BabyBuddyFilterView):
model = models.Note model = models.Note
template_name = 'core/note_list.html' template_name = "core/note_list.html"
permission_required = ('core.view_note',) permission_required = ("core.view_note",)
paginate_by = 10 paginate_by = 10
filterset_fields = ('child',) filterset_fields = ("child",)
class NoteAdd(CoreAddView): class NoteAdd(CoreAddView):
model = models.Note model = models.Note
permission_required = ('core.add_note',) permission_required = ("core.add_note",)
form_class = forms.NoteForm form_class = forms.NoteForm
success_url = reverse_lazy('core:note-list') success_url = reverse_lazy("core:note-list")
class NoteUpdate(CoreUpdateView): class NoteUpdate(CoreUpdateView):
model = models.Note model = models.Note
permission_required = ('core.change_note',) permission_required = ("core.change_note",)
form_class = forms.NoteForm form_class = forms.NoteForm
success_url = reverse_lazy('core:note-list') success_url = reverse_lazy("core:note-list")
class NoteDelete(CoreDeleteView): class NoteDelete(CoreDeleteView):
model = models.Note model = models.Note
permission_required = ('core.delete_note',) permission_required = ("core.delete_note",)
success_url = reverse_lazy('core:note-list') success_url = reverse_lazy("core:note-list")
class SleepList(PermissionRequiredMixin, BabyBuddyFilterView): class SleepList(PermissionRequiredMixin, BabyBuddyFilterView):
model = models.Sleep model = models.Sleep
template_name = 'core/sleep_list.html' template_name = "core/sleep_list.html"
permission_required = ('core.view_sleep',) permission_required = ("core.view_sleep",)
paginate_by = 10 paginate_by = 10
filterset_fields = ('child',) filterset_fields = ("child",)
class SleepAdd(CoreAddView): class SleepAdd(CoreAddView):
model = models.Sleep model = models.Sleep
permission_required = ('core.add_sleep',) permission_required = ("core.add_sleep",)
form_class = forms.SleepForm form_class = forms.SleepForm
success_url = reverse_lazy('core:sleep-list') success_url = reverse_lazy("core:sleep-list")
class SleepUpdate(CoreUpdateView): class SleepUpdate(CoreUpdateView):
model = models.Sleep model = models.Sleep
permission_required = ('core.change_sleep',) permission_required = ("core.change_sleep",)
form_class = forms.SleepForm form_class = forms.SleepForm
success_url = reverse_lazy('core:sleep-list') success_url = reverse_lazy("core:sleep-list")
class SleepDelete(CoreDeleteView): class SleepDelete(CoreDeleteView):
model = models.Sleep model = models.Sleep
permission_required = ('core.delete_sleep',) permission_required = ("core.delete_sleep",)
success_url = reverse_lazy('core:sleep-list') success_url = reverse_lazy("core:sleep-list")
class TemperatureList(PermissionRequiredMixin, BabyBuddyFilterView): class TemperatureList(PermissionRequiredMixin, BabyBuddyFilterView):
model = models.Temperature model = models.Temperature
template_name = 'core/temperature_list.html' template_name = "core/temperature_list.html"
permission_required = ('core.view_temperature',) permission_required = ("core.view_temperature",)
paginate_by = 10 paginate_by = 10
filterset_fields = ('child',) filterset_fields = ("child",)
class TemperatureAdd(CoreAddView): class TemperatureAdd(CoreAddView):
model = models.Temperature model = models.Temperature
permission_required = ('core.add_temperature',) permission_required = ("core.add_temperature",)
form_class = forms.TemperatureForm form_class = forms.TemperatureForm
success_url = reverse_lazy('core:temperature-list') success_url = reverse_lazy("core:temperature-list")
success_message = _('%(model)s reading added!') success_message = _("%(model)s reading added!")
class TemperatureUpdate(CoreUpdateView): class TemperatureUpdate(CoreUpdateView):
model = models.Temperature model = models.Temperature
permission_required = ('core.change_temperature',) permission_required = ("core.change_temperature",)
form_class = forms.TemperatureForm form_class = forms.TemperatureForm
success_url = reverse_lazy('core:temperature-list') success_url = reverse_lazy("core:temperature-list")
success_message = _('%(model)s reading for %(child)s updated.') success_message = _("%(model)s reading for %(child)s updated.")
class TemperatureDelete(CoreDeleteView): class TemperatureDelete(CoreDeleteView):
model = models.Temperature model = models.Temperature
permission_required = ('core.delete_temperature',) permission_required = ("core.delete_temperature",)
success_url = reverse_lazy('core:temperature-list') success_url = reverse_lazy("core:temperature-list")
class Timeline(LoginRequiredMixin, TemplateView): class Timeline(LoginRequiredMixin, TemplateView):
template_name = 'timeline/timeline.html' template_name = "timeline/timeline.html"
# Show the overall timeline or a child timeline if one Child instance. # Show the overall timeline or a child timeline if one Child instance.
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
children = models.Child.objects.count() children = models.Child.objects.count()
if children == 1: if children == 1:
return HttpResponseRedirect( return HttpResponseRedirect(
reverse( reverse("core:child", args={models.Child.objects.first().slug})
'core:child',
args={models.Child.objects.first().slug}
)
) )
return super(Timeline, self).get(request, *args, **kwargs) return super(Timeline, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(Timeline, self).get_context_data(**kwargs) context = super(Timeline, self).get_context_data(**kwargs)
date = self.request.GET.get('date', str(timezone.localdate())) date = self.request.GET.get("date", str(timezone.localdate()))
_prepare_timeline_context_data(context, date) _prepare_timeline_context_data(context, date)
return context return context
class TimerList(PermissionRequiredMixin, BabyBuddyFilterView): class TimerList(PermissionRequiredMixin, BabyBuddyFilterView):
model = models.Timer model = models.Timer
template_name = 'core/timer_list.html' template_name = "core/timer_list.html"
permission_required = ('core.view_timer',) permission_required = ("core.view_timer",)
paginate_by = 10 paginate_by = 10
filterset_fields = ('active', 'user') filterset_fields = ("active", "user")
class TimerDetail(PermissionRequiredMixin, DetailView): class TimerDetail(PermissionRequiredMixin, DetailView):
model = models.Timer model = models.Timer
permission_required = ('core.view_timer',) permission_required = ("core.view_timer",)
class TimerAdd(PermissionRequiredMixin, CreateView): class TimerAdd(PermissionRequiredMixin, CreateView):
model = models.Timer model = models.Timer
permission_required = ('core.add_timer',) permission_required = ("core.add_timer",)
form_class = forms.TimerForm form_class = forms.TimerForm
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super(TimerAdd, self).get_form_kwargs() kwargs = super(TimerAdd, self).get_form_kwargs()
kwargs.update({'user': self.request.user}) kwargs.update({"user": self.request.user})
return kwargs return kwargs
def get_success_url(self): def get_success_url(self):
return reverse('core:timer-detail', kwargs={'pk': self.object.pk}) return reverse("core:timer-detail", kwargs={"pk": self.object.pk})
class TimerUpdate(CoreUpdateView): class TimerUpdate(CoreUpdateView):
model = models.Timer model = models.Timer
permission_required = ('core.change_timer',) permission_required = ("core.change_timer",)
form_class = forms.TimerForm form_class = forms.TimerForm
success_url = reverse_lazy('core:timer-list') success_url = reverse_lazy("core:timer-list")
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super(TimerUpdate, self).get_form_kwargs() kwargs = super(TimerUpdate, self).get_form_kwargs()
kwargs.update({'user': self.request.user}) kwargs.update({"user": self.request.user})
return kwargs return kwargs
def get_success_url(self): def get_success_url(self):
instance = self.get_object() instance = self.get_object()
return reverse('core:timer-detail', kwargs={'pk': instance.pk}) return reverse("core:timer-detail", kwargs={"pk": instance.pk})
class TimerAddQuick(PermissionRequiredMixin, RedirectView): class TimerAddQuick(PermissionRequiredMixin, RedirectView):
http_method_names = ['post'] http_method_names = ["post"]
permission_required = ('core.add_timer',) permission_required = ("core.add_timer",)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
instance = models.Timer.objects.create(user=request.user) instance = models.Timer.objects.create(user=request.user)
@ -341,62 +336,62 @@ class TimerAddQuick(PermissionRequiredMixin, RedirectView):
instance.child = models.Child.objects.first() instance.child = models.Child.objects.first()
instance.save() instance.save()
self.url = request.GET.get( self.url = request.GET.get(
'next', reverse('core:timer-detail', args={instance.id})) "next", reverse("core:timer-detail", args={instance.id})
)
return super(TimerAddQuick, self).get(request, *args, **kwargs) return super(TimerAddQuick, self).get(request, *args, **kwargs)
class TimerRestart(PermissionRequiredMixin, RedirectView): class TimerRestart(PermissionRequiredMixin, RedirectView):
http_method_names = ['post'] http_method_names = ["post"]
permission_required = ('core.change_timer',) permission_required = ("core.change_timer",)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
instance = models.Timer.objects.get(id=kwargs['pk']) instance = models.Timer.objects.get(id=kwargs["pk"])
instance.restart() instance.restart()
messages.success(request, '{} restarted.'.format(instance)) messages.success(request, "{} restarted.".format(instance))
return super(TimerRestart, self).get(request, *args, **kwargs) return super(TimerRestart, self).get(request, *args, **kwargs)
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
return reverse('core:timer-detail', kwargs={'pk': kwargs['pk']}) return reverse("core:timer-detail", kwargs={"pk": kwargs["pk"]})
class TimerStop(PermissionRequiredMixin, SuccessMessageMixin, RedirectView): class TimerStop(PermissionRequiredMixin, SuccessMessageMixin, RedirectView):
http_method_names = ['post'] http_method_names = ["post"]
permission_required = ('core.change_timer',) permission_required = ("core.change_timer",)
success_message = _('%(timer)s stopped.') success_message = _("%(timer)s stopped.")
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
instance = models.Timer.objects.get(id=kwargs['pk']) instance = models.Timer.objects.get(id=kwargs["pk"])
instance.stop() instance.stop()
messages.success(request, '{} stopped.'.format(instance)) messages.success(request, "{} stopped.".format(instance))
return super(TimerStop, self).get(request, *args, **kwargs) return super(TimerStop, self).get(request, *args, **kwargs)
def get_redirect_url(self, *args, **kwargs): def get_redirect_url(self, *args, **kwargs):
return reverse('core:timer-detail', kwargs={'pk': kwargs['pk']}) return reverse("core:timer-detail", kwargs={"pk": kwargs["pk"]})
class TimerDelete(CoreDeleteView): class TimerDelete(CoreDeleteView):
model = models.Timer model = models.Timer
permission_required = ('core.delete_timer',) permission_required = ("core.delete_timer",)
success_url = reverse_lazy('core:timer-list') success_url = reverse_lazy("core:timer-list")
class TimerDeleteInactive(PermissionRequiredMixin, SuccessMessageMixin, class TimerDeleteInactive(PermissionRequiredMixin, SuccessMessageMixin, FormView):
FormView): permission_required = ("core.delete_timer",)
permission_required = ('core.delete_timer',)
form_class = Form form_class = Form
template_name = 'core/timer_confirm_delete_inactive.html' template_name = "core/timer_confirm_delete_inactive.html"
success_url = reverse_lazy('core:timer-list') success_url = reverse_lazy("core:timer-list")
success_message = _('All inactive timers deleted.') success_message = _("All inactive timers deleted.")
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs) kwargs = super().get_context_data(**kwargs)
kwargs['timer_count'] = self.get_instances().count() kwargs["timer_count"] = self.get_instances().count()
return kwargs return kwargs
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
# Redirect back to list if there are no inactive timers. # Redirect back to list if there are no inactive timers.
if self.get_instances().count() == 0: if self.get_instances().count() == 0:
messages.warning(request, _('No inactive timers exist.')) messages.warning(request, _("No inactive timers exist."))
return HttpResponseRedirect(self.success_url) return HttpResponseRedirect(self.success_url)
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
@ -411,142 +406,142 @@ class TimerDeleteInactive(PermissionRequiredMixin, SuccessMessageMixin,
class TummyTimeList(PermissionRequiredMixin, BabyBuddyFilterView): class TummyTimeList(PermissionRequiredMixin, BabyBuddyFilterView):
model = models.TummyTime model = models.TummyTime
template_name = 'core/tummytime_list.html' template_name = "core/tummytime_list.html"
permission_required = ('core.view_tummytime',) permission_required = ("core.view_tummytime",)
paginate_by = 10 paginate_by = 10
filterset_fields = ('child',) filterset_fields = ("child",)
class TummyTimeAdd(CoreAddView): class TummyTimeAdd(CoreAddView):
model = models.TummyTime model = models.TummyTime
permission_required = ('core.add_tummytime',) permission_required = ("core.add_tummytime",)
form_class = forms.TummyTimeForm form_class = forms.TummyTimeForm
success_url = reverse_lazy('core:tummytime-list') success_url = reverse_lazy("core:tummytime-list")
class TummyTimeUpdate(CoreUpdateView): class TummyTimeUpdate(CoreUpdateView):
model = models.TummyTime model = models.TummyTime
permission_required = ('core.change_tummytime',) permission_required = ("core.change_tummytime",)
form_class = forms.TummyTimeForm form_class = forms.TummyTimeForm
success_url = reverse_lazy('core:tummytime-list') success_url = reverse_lazy("core:tummytime-list")
class TummyTimeDelete(CoreDeleteView): class TummyTimeDelete(CoreDeleteView):
model = models.TummyTime model = models.TummyTime
permission_required = ('core.delete_tummytime',) permission_required = ("core.delete_tummytime",)
success_url = reverse_lazy('core:tummytime-list') success_url = reverse_lazy("core:tummytime-list")
class WeightList(PermissionRequiredMixin, BabyBuddyFilterView): class WeightList(PermissionRequiredMixin, BabyBuddyFilterView):
model = models.Weight model = models.Weight
template_name = 'core/weight_list.html' template_name = "core/weight_list.html"
permission_required = ('core.view_weight',) permission_required = ("core.view_weight",)
paginate_by = 10 paginate_by = 10
filterset_fields = ('child',) filterset_fields = ("child",)
class WeightAdd(CoreAddView): class WeightAdd(CoreAddView):
model = models.Weight model = models.Weight
permission_required = ('core.add_weight',) permission_required = ("core.add_weight",)
form_class = forms.WeightForm form_class = forms.WeightForm
success_url = reverse_lazy('core:weight-list') success_url = reverse_lazy("core:weight-list")
class WeightUpdate(CoreUpdateView): class WeightUpdate(CoreUpdateView):
model = models.Weight model = models.Weight
permission_required = ('core.change_weight',) permission_required = ("core.change_weight",)
form_class = forms.WeightForm form_class = forms.WeightForm
success_url = reverse_lazy('core:weight-list') success_url = reverse_lazy("core:weight-list")
class WeightDelete(CoreDeleteView): class WeightDelete(CoreDeleteView):
model = models.Weight model = models.Weight
permission_required = ('core.delete_weight',) permission_required = ("core.delete_weight",)
success_url = reverse_lazy('core:weight-list') success_url = reverse_lazy("core:weight-list")
class HeightList(PermissionRequiredMixin, BabyBuddyFilterView): class HeightList(PermissionRequiredMixin, BabyBuddyFilterView):
model = models.Height model = models.Height
template_name = 'core/height_list.html' template_name = "core/height_list.html"
permission_required = ('core.view_height',) permission_required = ("core.view_height",)
paginate_by = 10 paginate_by = 10
filterset_fields = ('child',) filterset_fields = ("child",)
class HeightAdd(CoreAddView): class HeightAdd(CoreAddView):
model = models.Height model = models.Height
permission_required = ('core.add_height',) permission_required = ("core.add_height",)
form_class = forms.HeightForm form_class = forms.HeightForm
success_url = reverse_lazy('core:height-list') success_url = reverse_lazy("core:height-list")
class HeightUpdate(CoreUpdateView): class HeightUpdate(CoreUpdateView):
model = models.Height model = models.Height
permission_required = ('core.change_height',) permission_required = ("core.change_height",)
form_class = forms.HeightForm form_class = forms.HeightForm
success_url = reverse_lazy('core:height-list') success_url = reverse_lazy("core:height-list")
class HeightDelete(CoreDeleteView): class HeightDelete(CoreDeleteView):
model = models.Height model = models.Height
permission_required = ('core.delete_height',) permission_required = ("core.delete_height",)
success_url = reverse_lazy('core:height-list') success_url = reverse_lazy("core:height-list")
class HeadCircumferenceList(PermissionRequiredMixin, BabyBuddyFilterView): class HeadCircumferenceList(PermissionRequiredMixin, BabyBuddyFilterView):
model = models.HeadCircumference model = models.HeadCircumference
template_name = 'core/head_circumference_list.html' template_name = "core/head_circumference_list.html"
permission_required = ('core.view_head_circumference',) permission_required = ("core.view_head_circumference",)
paginate_by = 10 paginate_by = 10
filterset_fields = ('child',) filterset_fields = ("child",)
class HeadCircumferenceAdd(CoreAddView): class HeadCircumferenceAdd(CoreAddView):
model = models.HeadCircumference model = models.HeadCircumference
template_name = 'core/head_circumference_form.html' template_name = "core/head_circumference_form.html"
permission_required = ('core.add_head_circumference',) permission_required = ("core.add_head_circumference",)
form_class = forms.HeadCircumferenceForm form_class = forms.HeadCircumferenceForm
success_url = reverse_lazy('core:head-circumference-list') success_url = reverse_lazy("core:head-circumference-list")
class HeadCircumferenceUpdate(CoreUpdateView): class HeadCircumferenceUpdate(CoreUpdateView):
model = models.HeadCircumference model = models.HeadCircumference
template_name = 'core/head_circumference_form.html' template_name = "core/head_circumference_form.html"
permission_required = ('core.change_head_circumference',) permission_required = ("core.change_head_circumference",)
form_class = forms.HeadCircumferenceForm form_class = forms.HeadCircumferenceForm
success_url = reverse_lazy('core:head-circumference-list') success_url = reverse_lazy("core:head-circumference-list")
class HeadCircumferenceDelete(CoreDeleteView): class HeadCircumferenceDelete(CoreDeleteView):
model = models.HeadCircumference model = models.HeadCircumference
template_name = 'core/head_circumference_delete.html' template_name = "core/head_circumference_delete.html"
permission_required = ('core.delete_head_circumference',) permission_required = ("core.delete_head_circumference",)
success_url = reverse_lazy('core:head-circumference-list') success_url = reverse_lazy("core:head-circumference-list")
class BMIList(PermissionRequiredMixin, BabyBuddyFilterView): class BMIList(PermissionRequiredMixin, BabyBuddyFilterView):
model = models.BMI model = models.BMI
template_name = 'core/bmi_list.html' template_name = "core/bmi_list.html"
permission_required = ('core.view_bmi',) permission_required = ("core.view_bmi",)
paginate_by = 10 paginate_by = 10
filterset_fields = ('child',) filterset_fields = ("child",)
class BMIAdd(CoreAddView): class BMIAdd(CoreAddView):
model = models.BMI model = models.BMI
permission_required = ('core.add_bmi',) permission_required = ("core.add_bmi",)
form_class = forms.BMIForm form_class = forms.BMIForm
success_url = reverse_lazy('core:bmi-list') success_url = reverse_lazy("core:bmi-list")
class BMIUpdate(CoreUpdateView): class BMIUpdate(CoreUpdateView):
model = models.BMI model = models.BMI
permission_required = ('core.change_bmi',) permission_required = ("core.change_bmi",)
form_class = forms.BMIForm form_class = forms.BMIForm
success_url = reverse_lazy('core:bmi-list') success_url = reverse_lazy("core:bmi-list")
class BMIDelete(CoreDeleteView): class BMIDelete(CoreDeleteView):
model = models.BMI model = models.BMI
permission_required = ('core.delete_bmi',) permission_required = ("core.delete_bmi",)
success_url = reverse_lazy('core:bmi-list') success_url = reverse_lazy("core:bmi-list")

View File

@ -13,39 +13,42 @@ register = template.Library()
def _hide_empty(context): def _hide_empty(context):
return context['request'].user.settings.dashboard_hide_empty return context["request"].user.settings.dashboard_hide_empty
def _filter_data_age(context, keyword="end"): def _filter_data_age(context, keyword="end"):
filter = {} filter = {}
if context['request'].user.settings.dashboard_hide_age: if context["request"].user.settings.dashboard_hide_age:
now = timezone.localtime() now = timezone.localtime()
start_time = now - context['request'].user.settings.dashboard_hide_age start_time = now - context["request"].user.settings.dashboard_hide_age
filter[keyword + "__range"] = (start_time, now) filter[keyword + "__range"] = (start_time, now)
return filter return filter
@register.inclusion_tag('cards/diaperchange_last.html', takes_context=True) @register.inclusion_tag("cards/diaperchange_last.html", takes_context=True)
def card_diaperchange_last(context, child): def card_diaperchange_last(context, child):
""" """
Information about the most recent diaper change. Information about the most recent diaper change.
:param child: an instance of the Child model. :param child: an instance of the Child model.
:returns: a dictionary with the most recent Diaper Change instance. :returns: a dictionary with the most recent Diaper Change instance.
""" """
instance = models.DiaperChange.objects.filter(child=child) \ instance = (
.filter(**_filter_data_age(context, "time")) \ models.DiaperChange.objects.filter(child=child)
.order_by('-time').first() .filter(**_filter_data_age(context, "time"))
.order_by("-time")
.first()
)
empty = not instance empty = not instance
return { return {
'type': 'diaperchange', "type": "diaperchange",
'change': instance, "change": instance,
'empty': empty, "empty": empty,
'hide_empty': _hide_empty(context) "hide_empty": _hide_empty(context),
} }
@register.inclusion_tag('cards/diaperchange_types.html', takes_context=True) @register.inclusion_tag("cards/diaperchange_types.html", takes_context=True)
def card_diaperchange_types(context, child, date=None): def card_diaperchange_types(context, child, date=None):
""" """
Creates a break down of wet and solid Diaper Change instances for the past Creates a break down of wet and solid Diaper Change instances for the past
@ -61,42 +64,46 @@ def card_diaperchange_types(context, child, date=None):
time = timezone.make_aware(time) time = timezone.make_aware(time)
stats = {} stats = {}
week_total = 0 week_total = 0
max_date = (time + timezone.timedelta(days=1)).replace( max_date = (time + timezone.timedelta(days=1)).replace(hour=0, minute=0, second=0)
hour=0, minute=0, second=0)
min_date = (max_date - timezone.timedelta(days=7)).replace( min_date = (max_date - timezone.timedelta(days=7)).replace(
hour=0, minute=0, second=0) hour=0, minute=0, second=0
)
for x in range(7): for x in range(7):
stats[x] = {'wet': 0.0, 'solid': 0.0} stats[x] = {"wet": 0.0, "solid": 0.0}
instances = models.DiaperChange.objects.filter(child=child) \ instances = (
.filter(time__gt=min_date).filter(time__lt=max_date).order_by('-time') models.DiaperChange.objects.filter(child=child)
.filter(time__gt=min_date)
.filter(time__lt=max_date)
.order_by("-time")
)
empty = len(instances) == 0 empty = len(instances) == 0
for instance in instances: for instance in instances:
key = (max_date - instance.time).days key = (max_date - instance.time).days
if instance.wet: if instance.wet:
stats[key]['wet'] += 1 stats[key]["wet"] += 1
if instance.solid: if instance.solid:
stats[key]['solid'] += 1 stats[key]["solid"] += 1
for key, info in stats.items(): for key, info in stats.items():
total = info['wet'] + info['solid'] total = info["wet"] + info["solid"]
week_total += total week_total += total
if total > 0: if total > 0:
stats[key]['wet_pct'] = info['wet'] / total * 100 stats[key]["wet_pct"] = info["wet"] / total * 100
stats[key]['solid_pct'] = info['solid'] / total * 100 stats[key]["solid_pct"] = info["solid"] / total * 100
return { return {
'type': 'diaperchange', "type": "diaperchange",
'stats': stats, "stats": stats,
'total': week_total, "total": week_total,
'empty': empty, "empty": empty,
'hide_empty': _hide_empty(context) "hide_empty": _hide_empty(context),
} }
@register.inclusion_tag('cards/feeding_day.html', takes_context=True) @register.inclusion_tag("cards/feeding_day.html", takes_context=True)
def card_feeding_day(context, child, date=None): def card_feeding_day(context, child, date=None):
""" """
Filters Feeding instances to get total amount for a specific date. Filters Feeding instances to get total amount for a specific date.
@ -108,90 +115,95 @@ def card_feeding_day(context, child, date=None):
date = timezone.localtime().date() date = timezone.localtime().date()
instances = models.Feeding.objects.filter(child=child).filter( instances = models.Feeding.objects.filter(child=child).filter(
start__year=date.year, start__year=date.year, start__month=date.month, start__day=date.day
start__month=date.month, ) | models.Feeding.objects.filter(child=child).filter(
start__day=date.day) \ end__year=date.year, end__month=date.month, end__day=date.day
| models.Feeding.objects.filter(child=child).filter( )
end__year=date.year,
end__month=date.month,
end__day=date.day)
total = sum([instance.amount for instance in instances if instance.amount]) total = sum([instance.amount for instance in instances if instance.amount])
count = len(instances) count = len(instances)
empty = len(instances) == 0 or total == 0 empty = len(instances) == 0 or total == 0
return { return {
'type': 'feeding', "type": "feeding",
'total': total, "total": total,
'count': count, "count": count,
'empty': empty, "empty": empty,
'hide_empty': _hide_empty(context) "hide_empty": _hide_empty(context),
} }
@register.inclusion_tag('cards/feeding_last.html', takes_context=True) @register.inclusion_tag("cards/feeding_last.html", takes_context=True)
def card_feeding_last(context, child): def card_feeding_last(context, child):
""" """
Information about the most recent feeding. Information about the most recent feeding.
:param child: an instance of the Child model. :param child: an instance of the Child model.
:returns: a dictionary with the most recent Feeding instance. :returns: a dictionary with the most recent Feeding instance.
""" """
instance = models.Feeding.objects.filter(child=child) \ instance = (
.filter(**_filter_data_age(context)) \ models.Feeding.objects.filter(child=child)
.order_by('-end').first() .filter(**_filter_data_age(context))
.order_by("-end")
.first()
)
empty = not instance empty = not instance
return { return {
'type': 'feeding', "type": "feeding",
'feeding': instance, "feeding": instance,
'empty': empty, "empty": empty,
'hide_empty': _hide_empty(context) "hide_empty": _hide_empty(context),
} }
@register.inclusion_tag('cards/feeding_last_method.html', takes_context=True) @register.inclusion_tag("cards/feeding_last_method.html", takes_context=True)
def card_feeding_last_method(context, child): def card_feeding_last_method(context, child):
""" """
Information about the three most recent feeding methods. Information about the three most recent feeding methods.
:param child: an instance of the Child model. :param child: an instance of the Child model.
:returns: a dictionary with the most recent Feeding instances. :returns: a dictionary with the most recent Feeding instances.
""" """
instances = models.Feeding.objects.filter(child=child) \ instances = (
.filter(**_filter_data_age(context)) \ models.Feeding.objects.filter(child=child)
.order_by('-end')[:3] .filter(**_filter_data_age(context))
.order_by("-end")[:3]
)
num_unique_methods = len({i.method for i in instances}) num_unique_methods = len({i.method for i in instances})
empty = num_unique_methods <= 1 empty = num_unique_methods <= 1
# Results are reversed for carousel forward/back behavior. # Results are reversed for carousel forward/back behavior.
return { return {
'type': 'feeding', "type": "feeding",
'feedings': list(reversed(instances)), "feedings": list(reversed(instances)),
'empty': empty, "empty": empty,
'hide_empty': _hide_empty(context) "hide_empty": _hide_empty(context),
} }
@register.inclusion_tag('cards/sleep_last.html', takes_context=True) @register.inclusion_tag("cards/sleep_last.html", takes_context=True)
def card_sleep_last(context, child): def card_sleep_last(context, child):
""" """
Information about the most recent sleep entry. Information about the most recent sleep entry.
:param child: an instance of the Child model. :param child: an instance of the Child model.
:returns: a dictionary with the most recent Sleep instance. :returns: a dictionary with the most recent Sleep instance.
""" """
instance = models.Sleep.objects.filter(child=child) \ instance = (
.filter(**_filter_data_age(context)) \ models.Sleep.objects.filter(child=child)
.order_by('-end').first() .filter(**_filter_data_age(context))
.order_by("-end")
.first()
)
empty = not instance empty = not instance
return { return {
'type': 'sleep', "type": "sleep",
'sleep': instance, "sleep": instance,
'empty': empty, "empty": empty,
'hide_empty': _hide_empty(context) "hide_empty": _hide_empty(context),
} }
@register.inclusion_tag('cards/sleep_day.html', takes_context=True) @register.inclusion_tag("cards/sleep_day.html", takes_context=True)
def card_sleep_day(context, child, date=None): def card_sleep_day(context, child, date=None):
""" """
Filters Sleep instances to get count and total values for a specific date. Filters Sleep instances to get count and total values for a specific date.
@ -202,12 +214,10 @@ def card_sleep_day(context, child, date=None):
if not date: if not date:
date = timezone.localtime().date() date = timezone.localtime().date()
instances = models.Sleep.objects.filter(child=child).filter( instances = models.Sleep.objects.filter(child=child).filter(
start__year=date.year, start__year=date.year, start__month=date.month, start__day=date.day
start__month=date.month, ) | models.Sleep.objects.filter(child=child).filter(
start__day=date.day) | models.Sleep.objects.filter(child=child).filter( end__year=date.year, end__month=date.month, end__day=date.day
end__year=date.year, )
end__month=date.month,
end__day=date.day)
empty = len(instances) == 0 empty = len(instances) == 0
total = timezone.timedelta(seconds=0) total = timezone.timedelta(seconds=0)
@ -216,23 +226,24 @@ def card_sleep_day(context, child, date=None):
end = timezone.localtime(instance.end) end = timezone.localtime(instance.end)
# Account for dates crossing midnight. # Account for dates crossing midnight.
if start.date() != date: if start.date() != date:
start = start.replace(year=end.year, month=end.month, day=end.day, start = start.replace(
hour=0, minute=0, second=0) year=end.year, month=end.month, day=end.day, hour=0, minute=0, second=0
)
total += end - start total += end - start
count = len(instances) count = len(instances)
return { return {
'type': 'sleep', "type": "sleep",
'total': total, "total": total,
'count': count, "count": count,
'empty': empty, "empty": empty,
'hide_empty': _hide_empty(context) "hide_empty": _hide_empty(context),
} }
@register.inclusion_tag('cards/sleep_naps_day.html', takes_context=True) @register.inclusion_tag("cards/sleep_naps_day.html", takes_context=True)
def card_sleep_naps_day(context, child, date=None): def card_sleep_naps_day(context, child, date=None):
""" """
Filters Sleep instances categorized as naps and generates statistics for a Filters Sleep instances categorized as naps and generates statistics for a
@ -244,23 +255,22 @@ def card_sleep_naps_day(context, child, date=None):
if not date: if not date:
date = timezone.localtime().date() date = timezone.localtime().date()
instances = models.Sleep.naps.filter(child=child).filter( instances = models.Sleep.naps.filter(child=child).filter(
start__year=date.year, start__year=date.year, start__month=date.month, start__day=date.day
start__month=date.month, ) | models.Sleep.naps.filter(child=child).filter(
start__day=date.day) | models.Sleep.naps.filter(child=child).filter( end__year=date.year, end__month=date.month, end__day=date.day
end__year=date.year, )
end__month=date.month,
end__day=date.day)
empty = len(instances) == 0 empty = len(instances) == 0
return { return {
'type': 'sleep', "type": "sleep",
'total': instances.aggregate(Sum('duration'))['duration__sum'], "total": instances.aggregate(Sum("duration"))["duration__sum"],
'count': len(instances), "count": len(instances),
'empty': empty, 'hide_empty': _hide_empty(context) "empty": empty,
"hide_empty": _hide_empty(context),
} }
@register.inclusion_tag('cards/statistics.html', takes_context=True) @register.inclusion_tag("cards/statistics.html", takes_context=True)
def card_statistics(context, child): def card_statistics(context, child):
""" """
Statistics data for all models. Statistics data for all models.
@ -271,76 +281,102 @@ def card_statistics(context, child):
changes = _diaperchange_statistics(child) changes = _diaperchange_statistics(child)
if changes: if changes:
stats.append({ stats.append(
'type': 'duration', {
'stat': changes['btwn_average'], "type": "duration",
'title': _('Diaper change frequency')}) "stat": changes["btwn_average"],
"title": _("Diaper change frequency"),
}
)
feedings = _feeding_statistics(child) feedings = _feeding_statistics(child)
if feedings: if feedings:
for item in feedings: for item in feedings:
stats.append({ stats.append(
'type': 'duration', {
'stat': item['btwn_average'], "type": "duration",
'title': item['title']}) "stat": item["btwn_average"],
"title": item["title"],
}
)
naps = _nap_statistics(child) naps = _nap_statistics(child)
if naps: if naps:
stats.append({ stats.append(
'type': 'duration', {
'stat': naps['average'], "type": "duration",
'title': _('Average nap duration')}) "stat": naps["average"],
stats.append({ "title": _("Average nap duration"),
'type': 'float', }
'stat': naps['avg_per_day'], )
'title': _('Average naps per day')}) stats.append(
{
"type": "float",
"stat": naps["avg_per_day"],
"title": _("Average naps per day"),
}
)
sleep = _sleep_statistics(child) sleep = _sleep_statistics(child)
if sleep: if sleep:
stats.append({ stats.append(
'type': 'duration', {
'stat': sleep['average'], "type": "duration",
'title': _('Average sleep duration')}) "stat": sleep["average"],
stats.append({ "title": _("Average sleep duration"),
'type': 'duration', }
'stat': sleep['btwn_average'], )
'title': _('Average awake duration')}) stats.append(
{
"type": "duration",
"stat": sleep["btwn_average"],
"title": _("Average awake duration"),
}
)
weight = _weight_statistics(child) weight = _weight_statistics(child)
if weight: if weight:
stats.append({ stats.append(
'type': 'float', {
'stat': weight['change_weekly'], "type": "float",
'title': _('Weight change per week')}) "stat": weight["change_weekly"],
"title": _("Weight change per week"),
}
)
height = _height_statistics(child) height = _height_statistics(child)
if height: if height:
stats.append({ stats.append(
'type': 'float', {
'stat': height['change_weekly'], "type": "float",
'title': _('Height change per week')}) "stat": height["change_weekly"],
"title": _("Height change per week"),
}
)
head_circumference = _head_circumference_statistics(child) head_circumference = _head_circumference_statistics(child)
if head_circumference: if head_circumference:
stats.append({ stats.append(
'type': 'float', {
'stat': head_circumference['change_weekly'], "type": "float",
'title': _('Head circumference change per week')}) "stat": head_circumference["change_weekly"],
"title": _("Head circumference change per week"),
}
)
bmi = _bmi_statistics(child) bmi = _bmi_statistics(child)
if bmi: if bmi:
stats.append({ stats.append(
'type': 'float', {
'stat': bmi['change_weekly'], "type": "float",
'title': _('BMI change per week')}) "stat": bmi["change_weekly"],
"title": _("BMI change per week"),
}
)
empty = len(stats) == 0 empty = len(stats) == 0
return { return {"stats": stats, "empty": empty, "hide_empty": _hide_empty(context)}
'stats': stats,
'empty': empty,
'hide_empty': _hide_empty(context)
}
def _diaperchange_statistics(child): def _diaperchange_statistics(child):
@ -349,23 +385,23 @@ def _diaperchange_statistics(child):
:param child: an instance of the Child model. :param child: an instance of the Child model.
:returns: a dictionary of statistics. :returns: a dictionary of statistics.
""" """
instances = models.DiaperChange.objects.filter(child=child) \ instances = models.DiaperChange.objects.filter(child=child).order_by("time")
.order_by('time')
if len(instances) == 0: if len(instances) == 0:
return False return False
changes = { changes = {
'btwn_total': timezone.timedelta(0), "btwn_total": timezone.timedelta(0),
'btwn_count': instances.count() - 1, "btwn_count": instances.count() - 1,
'btwn_average': 0.0} "btwn_average": 0.0,
}
last_instance = None last_instance = None
for instance in instances: for instance in instances:
if last_instance: if last_instance:
changes['btwn_total'] += instance.time - last_instance.time changes["btwn_total"] += instance.time - last_instance.time
last_instance = instance last_instance = instance
if changes['btwn_count'] > 0: if changes["btwn_count"] > 0:
changes['btwn_average'] = changes['btwn_total'] / changes['btwn_count'] changes["btwn_average"] = changes["btwn_total"] / changes["btwn_count"]
return changes return changes
@ -378,26 +414,26 @@ def _feeding_statistics(child):
""" """
feedings = [ feedings = [
{ {
'start': timezone.now() - timezone.timedelta(days=3), "start": timezone.now() - timezone.timedelta(days=3),
'title': _('Feeding frequency (past 3 days)') "title": _("Feeding frequency (past 3 days)"),
}, },
{ {
'start': timezone.now() - timezone.timedelta(weeks=2), "start": timezone.now() - timezone.timedelta(weeks=2),
'title': _('Feeding frequency (past 2 weeks)') "title": _("Feeding frequency (past 2 weeks)"),
}, },
{ {
'start': timezone.make_aware( "start": timezone.make_aware(
datetime.combine(date.min, time(0, 0)) datetime.combine(date.min, time(0, 0)) + timezone.timedelta(days=1)
+ timezone.timedelta(days=1)), ),
'title': _('Feeding frequency') "title": _("Feeding frequency"),
} },
] ]
for timespan in feedings: for timespan in feedings:
timespan['btwn_total'] = timezone.timedelta(0) timespan["btwn_total"] = timezone.timedelta(0)
timespan['btwn_count'] = 0 timespan["btwn_count"] = 0
timespan['btwn_average'] = 0.0 timespan["btwn_average"] = 0.0
instances = models.Feeding.objects.filter(child=child).order_by('start') instances = models.Feeding.objects.filter(child=child).order_by("start")
if len(instances) == 0: if len(instances) == 0:
return False return False
last_instance = None last_instance = None
@ -405,16 +441,14 @@ def _feeding_statistics(child):
for instance in instances: for instance in instances:
if last_instance: if last_instance:
for timespan in feedings: for timespan in feedings:
if last_instance.start > timespan['start']: if last_instance.start > timespan["start"]:
timespan['btwn_total'] += (instance.start timespan["btwn_total"] += instance.start - last_instance.end
- last_instance.end) timespan["btwn_count"] += 1
timespan['btwn_count'] += 1
last_instance = instance last_instance = instance
for timespan in feedings: for timespan in feedings:
if timespan['btwn_count'] > 0: if timespan["btwn_count"] > 0:
timespan['btwn_average'] = \ timespan["btwn_average"] = timespan["btwn_total"] / timespan["btwn_count"]
timespan['btwn_total'] / timespan['btwn_count']
return feedings return feedings
@ -424,21 +458,26 @@ def _nap_statistics(child):
:param child: an instance of the Child model. :param child: an instance of the Child model.
:returns: a dictionary of statistics. :returns: a dictionary of statistics.
""" """
instances = models.Sleep.naps.filter(child=child).order_by('start') instances = models.Sleep.naps.filter(child=child).order_by("start")
if len(instances) == 0: if len(instances) == 0:
return False return False
naps = { naps = {
'total': instances.aggregate(Sum('duration'))['duration__sum'], "total": instances.aggregate(Sum("duration"))["duration__sum"],
'count': instances.count(), "count": instances.count(),
'average': 0.0, "average": 0.0,
'avg_per_day': 0.0} "avg_per_day": 0.0,
if naps['count'] > 0: }
naps['average'] = naps['total'] / naps['count'] if naps["count"] > 0:
naps["average"] = naps["total"] / naps["count"]
naps_avg = instances.annotate(date=TruncDate('start')).values('date') \ naps_avg = (
.annotate(naps_count=Count('id')).order_by() \ instances.annotate(date=TruncDate("start"))
.aggregate(Avg('naps_count')) .values("date")
naps['avg_per_day'] = naps_avg['naps_count__avg'] .annotate(naps_count=Count("id"))
.order_by()
.aggregate(Avg("naps_count"))
)
naps["avg_per_day"] = naps_avg["naps_count__avg"]
return naps return naps
@ -449,28 +488,29 @@ def _sleep_statistics(child):
:param child: an instance of the Child model. :param child: an instance of the Child model.
:returns: a dictionary of statistics. :returns: a dictionary of statistics.
""" """
instances = models.Sleep.objects.filter(child=child).order_by('start') instances = models.Sleep.objects.filter(child=child).order_by("start")
if len(instances) == 0: if len(instances) == 0:
return False return False
sleep = { sleep = {
'total': instances.aggregate(Sum('duration'))['duration__sum'], "total": instances.aggregate(Sum("duration"))["duration__sum"],
'count': instances.count(), "count": instances.count(),
'average': 0.0, "average": 0.0,
'btwn_total': timezone.timedelta(0), "btwn_total": timezone.timedelta(0),
'btwn_count': instances.count() - 1, "btwn_count": instances.count() - 1,
'btwn_average': 0.0} "btwn_average": 0.0,
}
last_instance = None last_instance = None
for instance in instances: for instance in instances:
if last_instance: if last_instance:
sleep['btwn_total'] += instance.start - last_instance.end sleep["btwn_total"] += instance.start - last_instance.end
last_instance = instance last_instance = instance
if sleep['count'] > 0: if sleep["count"] > 0:
sleep['average'] = sleep['total'] / sleep['count'] sleep["average"] = sleep["total"] / sleep["count"]
if sleep['btwn_count'] > 0: if sleep["btwn_count"] > 0:
sleep['btwn_average'] = sleep['btwn_total'] / sleep['btwn_count'] sleep["btwn_average"] = sleep["btwn_total"] / sleep["btwn_count"]
return sleep return sleep
@ -481,9 +521,9 @@ def _weight_statistics(child):
:param child: an instance of the Child model. :param child: an instance of the Child model.
:returns: a dictionary of statistics. :returns: a dictionary of statistics.
""" """
weight = {'change_weekly': 0.0} weight = {"change_weekly": 0.0}
instances = models.Weight.objects.filter(child=child).order_by('-date') instances = models.Weight.objects.filter(child=child).order_by("-date")
if len(instances) == 0: if len(instances) == 0:
return False return False
@ -493,7 +533,7 @@ def _weight_statistics(child):
if newest != oldest: if newest != oldest:
weight_change = newest.weight - oldest.weight weight_change = newest.weight - oldest.weight
weeks = (newest.date - oldest.date).days / 7 weeks = (newest.date - oldest.date).days / 7
weight['change_weekly'] = weight_change/weeks weight["change_weekly"] = weight_change / weeks
return weight return weight
@ -504,9 +544,9 @@ def _height_statistics(child):
:param child: an instance of the Child model. :param child: an instance of the Child model.
:returns: a dictionary of statistics. :returns: a dictionary of statistics.
""" """
height = {'change_weekly': 0.0} height = {"change_weekly": 0.0}
instances = models.Height.objects.filter(child=child).order_by('-date') instances = models.Height.objects.filter(child=child).order_by("-date")
if len(instances) == 0: if len(instances) == 0:
return False return False
@ -516,7 +556,7 @@ def _height_statistics(child):
if newest != oldest: if newest != oldest:
height_change = newest.height - oldest.height height_change = newest.height - oldest.height
weeks = (newest.date - oldest.date).days / 7 weeks = (newest.date - oldest.date).days / 7
height['change_weekly'] = height_change/weeks height["change_weekly"] = height_change / weeks
return height return height
@ -527,11 +567,9 @@ def _head_circumference_statistics(child):
:param child: an instance of the Child model. :param child: an instance of the Child model.
:returns: a dictionary of statistics. :returns: a dictionary of statistics.
""" """
head_circumference = {'change_weekly': 0.0} head_circumference = {"change_weekly": 0.0}
instances = models.HeadCircumference.objects.filter( instances = models.HeadCircumference.objects.filter(child=child).order_by("-date")
child=child
).order_by('-date')
if len(instances) == 0: if len(instances) == 0:
return False return False
@ -541,7 +579,7 @@ def _head_circumference_statistics(child):
if newest != oldest: if newest != oldest:
hc_change = newest.head_circumference - oldest.head_circumference hc_change = newest.head_circumference - oldest.head_circumference
weeks = (newest.date - oldest.date).days / 7 weeks = (newest.date - oldest.date).days / 7
head_circumference['change_weekly'] = hc_change/weeks head_circumference["change_weekly"] = hc_change / weeks
return head_circumference return head_circumference
@ -552,9 +590,9 @@ def _bmi_statistics(child):
:param child: an instance of the Child model. :param child: an instance of the Child model.
:returns: a dictionary of statistics. :returns: a dictionary of statistics.
""" """
bmi = {'change_weekly': 0.0} bmi = {"change_weekly": 0.0}
instances = models.BMI.objects.filter(child=child).order_by('-date') instances = models.BMI.objects.filter(child=child).order_by("-date")
if len(instances) == 0: if len(instances) == 0:
return False return False
@ -564,12 +602,12 @@ def _bmi_statistics(child):
if newest != oldest: if newest != oldest:
bmi_change = newest.bmi - oldest.bmi bmi_change = newest.bmi - oldest.bmi
weeks = (newest.date - oldest.date).days / 7 weeks = (newest.date - oldest.date).days / 7
bmi['change_weekly'] = bmi_change/weeks bmi["change_weekly"] = bmi_change / weeks
return bmi return bmi
@register.inclusion_tag('cards/timer_list.html', takes_context=True) @register.inclusion_tag("cards/timer_list.html", takes_context=True)
def card_timer_list(context, child=None): def card_timer_list(context, child=None):
""" """
Filters for currently active Timer instances, optionally by child. Filters for currently active Timer instances, optionally by child.
@ -579,42 +617,44 @@ def card_timer_list(context, child=None):
if child: if child:
# Get active instances for the selected child _or_ None (no child). # Get active instances for the selected child _or_ None (no child).
instances = models.Timer.objects.filter( instances = models.Timer.objects.filter(
Q(active=True), Q(active=True), Q(child=child) | Q(child=None)
Q(child=child) | Q(child=None) ).order_by("-start")
).order_by('-start')
else: else:
instances = models.Timer.objects.filter(active=True).order_by('-start') instances = models.Timer.objects.filter(active=True).order_by("-start")
empty = len(instances) == 0 empty = len(instances) == 0
return { return {
'type': 'timer', "type": "timer",
'instances': list(instances), "instances": list(instances),
'empty': empty, "empty": empty,
'hide_empty': _hide_empty(context) "hide_empty": _hide_empty(context),
} }
@register.inclusion_tag('cards/tummytime_last.html', takes_context=True) @register.inclusion_tag("cards/tummytime_last.html", takes_context=True)
def card_tummytime_last(context, child): def card_tummytime_last(context, child):
""" """
Filters the most recent tummy time. Filters the most recent tummy time.
:param child: an instance of the Child model. :param child: an instance of the Child model.
:returns: a dictionary with the most recent Tummy Time instance. :returns: a dictionary with the most recent Tummy Time instance.
""" """
instance = models.TummyTime.objects.filter(child=child) \ instance = (
.filter(**_filter_data_age(context)) \ models.TummyTime.objects.filter(child=child)
.order_by('-end').first() .filter(**_filter_data_age(context))
.order_by("-end")
.first()
)
empty = not instance empty = not instance
return { return {
'type': 'tummytime', "type": "tummytime",
'tummytime': instance, "tummytime": instance,
'empty': empty, "empty": empty,
'hide_empty': _hide_empty(context) "hide_empty": _hide_empty(context),
} }
@register.inclusion_tag('cards/tummytime_day.html', takes_context=True) @register.inclusion_tag("cards/tummytime_day.html", takes_context=True)
def card_tummytime_day(context, child, date=None): def card_tummytime_day(context, child, date=None):
""" """
Filters Tummy Time instances and generates statistics for a specific date. Filters Tummy Time instances and generates statistics for a specific date.
@ -625,22 +665,19 @@ def card_tummytime_day(context, child, date=None):
if not date: if not date:
date = timezone.localtime().date() date = timezone.localtime().date()
instances = models.TummyTime.objects.filter( instances = models.TummyTime.objects.filter(
child=child, end__year=date.year, end__month=date.month, child=child, end__year=date.year, end__month=date.month, end__day=date.day
end__day=date.day).order_by('-end') ).order_by("-end")
empty = len(instances) == 0 empty = len(instances) == 0
stats = { stats = {"total": timezone.timedelta(seconds=0), "count": instances.count()}
'total': timezone.timedelta(seconds=0),
'count': instances.count()
}
for instance in instances: for instance in instances:
stats['total'] += timezone.timedelta(seconds=instance.duration.seconds) stats["total"] += timezone.timedelta(seconds=instance.duration.seconds)
return { return {
'type': 'tummytime', "type": "tummytime",
'stats': stats, "stats": stats,
'instances': instances, "instances": instances,
'last': instances.first(), "last": instances.first(),
'empty': empty, "empty": empty,
'hide_empty': _hide_empty(context) "hide_empty": _hide_empty(context),
} }

View File

@ -18,275 +18,257 @@ class MockUserRequest:
class TemplateTagsTestCase(TestCase): class TemplateTagsTestCase(TestCase):
fixtures = ['tests.json'] fixtures = ["tests.json"]
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super(TemplateTagsTestCase, cls).setUpClass() super(TemplateTagsTestCase, cls).setUpClass()
cls.child = models.Child.objects.first() cls.child = models.Child.objects.first()
cls.context = {'request': MockUserRequest(User.objects.first())} cls.context = {"request": MockUserRequest(User.objects.first())}
# Ensure timezone matches the one defined by fixtures. # Ensure timezone matches the one defined by fixtures.
user_timezone = Settings.objects.first().timezone user_timezone = Settings.objects.first().timezone
timezone.activate(pytz.timezone(user_timezone)) timezone.activate(pytz.timezone(user_timezone))
# Test file data uses a basis date of 2017-11-18. # Test file data uses a basis date of 2017-11-18.
date = timezone.localtime().strptime('2017-11-18', '%Y-%m-%d') date = timezone.localtime().strptime("2017-11-18", "%Y-%m-%d")
cls.date = timezone.make_aware(date) cls.date = timezone.make_aware(date)
def test_hide_empty(self): def test_hide_empty(self):
request = MockUserRequest(User.objects.first()) request = MockUserRequest(User.objects.first())
request.user.settings.dashboard_hide_empty = True request.user.settings.dashboard_hide_empty = True
context = {'request': request} context = {"request": request}
hide_empty = cards._hide_empty(context) hide_empty = cards._hide_empty(context)
self.assertTrue(hide_empty) self.assertTrue(hide_empty)
def test_filter_data_age_none(self): def test_filter_data_age_none(self):
request = MockUserRequest(User.objects.first()) request = MockUserRequest(User.objects.first())
request.user.settings.dashboard_hide_age = None request.user.settings.dashboard_hide_age = None
context = {'request': request} context = {"request": request}
filter_data_age = cards._filter_data_age(context) filter_data_age = cards._filter_data_age(context)
self.assertFalse(len(filter_data_age)) self.assertFalse(len(filter_data_age))
@mock.patch('dashboard.templatetags.cards.timezone') @mock.patch("dashboard.templatetags.cards.timezone")
def test_filter_data_age_one_day(self, mocked_timezone): def test_filter_data_age_one_day(self, mocked_timezone):
request = MockUserRequest(User.objects.first()) request = MockUserRequest(User.objects.first())
request.user.settings.dashboard_hide_age = timezone.timedelta(days=1) request.user.settings.dashboard_hide_age = timezone.timedelta(days=1)
context = {'request': request} context = {"request": request}
mocked_timezone.localtime.return_value = \ mocked_timezone.localtime.return_value = timezone.localtime().strptime(
timezone.localtime().strptime('2017-11-18', '%Y-%m-%d') "2017-11-18", "%Y-%m-%d"
)
filter_data_age = cards._filter_data_age(context, keyword="time") filter_data_age = cards._filter_data_age(context, keyword="time")
self.assertIn("time__range", filter_data_age) self.assertIn("time__range", filter_data_age)
self.assertEqual( self.assertEqual(
filter_data_age["time__range"][0], filter_data_age["time__range"][0],
timezone.localtime().strptime('2017-11-17', '%Y-%m-%d')) timezone.localtime().strptime("2017-11-17", "%Y-%m-%d"),
)
self.assertEqual( self.assertEqual(
filter_data_age["time__range"][1], filter_data_age["time__range"][1],
timezone.localtime().strptime('2017-11-18', '%Y-%m-%d')) timezone.localtime().strptime("2017-11-18", "%Y-%m-%d"),
)
def test_card_diaperchange_last(self): def test_card_diaperchange_last(self):
data = cards.card_diaperchange_last(self.context, self.child) data = cards.card_diaperchange_last(self.context, self.child)
self.assertEqual(data['type'], 'diaperchange') self.assertEqual(data["type"], "diaperchange")
self.assertFalse(data['empty']) self.assertFalse(data["empty"])
self.assertFalse(data['hide_empty']) self.assertFalse(data["hide_empty"])
self.assertIsInstance(data['change'], models.DiaperChange) self.assertIsInstance(data["change"], models.DiaperChange)
self.assertEqual(data['change'], models.DiaperChange.objects.first()) self.assertEqual(data["change"], models.DiaperChange.objects.first())
@mock.patch('dashboard.templatetags.cards.timezone') @mock.patch("dashboard.templatetags.cards.timezone")
def test_card_diaperchange_last_filter_age(self, mocked_timezone): def test_card_diaperchange_last_filter_age(self, mocked_timezone):
request = MockUserRequest(User.objects.first()) request = MockUserRequest(User.objects.first())
request.user.settings.dashboard_hide_age = timezone.timedelta(days=1) request.user.settings.dashboard_hide_age = timezone.timedelta(days=1)
context = {'request': request} context = {"request": request}
time = timezone.localtime().strptime('2017-11-10', '%Y-%m-%d') time = timezone.localtime().strptime("2017-11-10", "%Y-%m-%d")
mocked_timezone.localtime.return_value = timezone.make_aware(time) mocked_timezone.localtime.return_value = timezone.make_aware(time)
data = cards.card_diaperchange_last(context, self.child) data = cards.card_diaperchange_last(context, self.child)
self.assertTrue(data['empty']) self.assertTrue(data["empty"])
def test_card_diaperchange_types(self): def test_card_diaperchange_types(self):
data = cards.card_diaperchange_types( data = cards.card_diaperchange_types(self.context, self.child, self.date)
self.context, self.assertEqual(data["type"], "diaperchange")
self.child,
self.date)
self.assertEqual(data['type'], 'diaperchange')
stats = { stats = {
0: {'wet_pct': 50.0, 'solid_pct': 50.0, 'solid': 1, 'wet': 1}, 0: {"wet_pct": 50.0, "solid_pct": 50.0, "solid": 1, "wet": 1},
1: {'wet_pct': 0.0, 'solid_pct': 100.0, 'solid': 2, 'wet': 0}, 1: {"wet_pct": 0.0, "solid_pct": 100.0, "solid": 2, "wet": 0},
2: {'wet_pct': 100.0, 'solid_pct': 0.0, 'solid': 0, 'wet': 2}, 2: {"wet_pct": 100.0, "solid_pct": 0.0, "solid": 0, "wet": 2},
3: {'wet_pct': 75.0, 'solid_pct': 25.0, 'solid': 1, 'wet': 3}, 3: {"wet_pct": 75.0, "solid_pct": 25.0, "solid": 1, "wet": 3},
4: {'wet_pct': 100.0, 'solid_pct': 0.0, 'solid': 0, 'wet': 1}, 4: {"wet_pct": 100.0, "solid_pct": 0.0, "solid": 0, "wet": 1},
5: {'wet_pct': 100.0, 'solid_pct': 0.0, 'solid': 0, 'wet': 2}, 5: {"wet_pct": 100.0, "solid_pct": 0.0, "solid": 0, "wet": 2},
6: {'wet_pct': 100.0, 'solid_pct': 0.0, 'solid': 0, 'wet': 1} 6: {"wet_pct": 100.0, "solid_pct": 0.0, "solid": 0, "wet": 1},
} }
self.assertEqual(data['stats'], stats) self.assertEqual(data["stats"], stats)
def test_card_feeding_day(self): def test_card_feeding_day(self):
data = cards.card_feeding_day(self.context, self.child, self.date) data = cards.card_feeding_day(self.context, self.child, self.date)
self.assertEqual(data['type'], 'feeding') self.assertEqual(data["type"], "feeding")
self.assertFalse(data['empty']) self.assertFalse(data["empty"])
self.assertFalse(data['hide_empty']) self.assertFalse(data["hide_empty"])
self.assertEqual(data['total'], 2.5) self.assertEqual(data["total"], 2.5)
self.assertEqual(data['count'], 3) self.assertEqual(data["count"], 3)
def test_card_feeding_last(self): def test_card_feeding_last(self):
data = cards.card_feeding_last(self.context, self.child) data = cards.card_feeding_last(self.context, self.child)
self.assertEqual(data['type'], 'feeding') self.assertEqual(data["type"], "feeding")
self.assertFalse(data['empty']) self.assertFalse(data["empty"])
self.assertFalse(data['hide_empty']) self.assertFalse(data["hide_empty"])
self.assertIsInstance(data['feeding'], models.Feeding) self.assertIsInstance(data["feeding"], models.Feeding)
self.assertEqual(data['feeding'], models.Feeding.objects.first()) self.assertEqual(data["feeding"], models.Feeding.objects.first())
def test_card_feeding_last_method(self): def test_card_feeding_last_method(self):
data = cards.card_feeding_last_method(self.context, self.child) data = cards.card_feeding_last_method(self.context, self.child)
self.assertEqual(data['type'], 'feeding') self.assertEqual(data["type"], "feeding")
self.assertFalse(data['empty']) self.assertFalse(data["empty"])
self.assertFalse(data['hide_empty']) self.assertFalse(data["hide_empty"])
self.assertEqual(len(data['feedings']), 3) self.assertEqual(len(data["feedings"]), 3)
for feeding in data['feedings']: for feeding in data["feedings"]:
self.assertIsInstance(feeding, models.Feeding) self.assertIsInstance(feeding, models.Feeding)
self.assertEqual( self.assertEqual(
data['feedings'][2].method, data["feedings"][2].method, models.Feeding.objects.first().method
models.Feeding.objects.first().method) )
def test_card_sleep_last(self): def test_card_sleep_last(self):
data = cards.card_sleep_last(self.context, self.child) data = cards.card_sleep_last(self.context, self.child)
self.assertEqual(data['type'], 'sleep') self.assertEqual(data["type"], "sleep")
self.assertFalse(data['empty']) self.assertFalse(data["empty"])
self.assertFalse(data['hide_empty']) self.assertFalse(data["hide_empty"])
self.assertIsInstance(data['sleep'], models.Sleep) self.assertIsInstance(data["sleep"], models.Sleep)
self.assertEqual(data['sleep'], models.Sleep.objects.first()) self.assertEqual(data["sleep"], models.Sleep.objects.first())
def test_card_sleep_last_empty(self): def test_card_sleep_last_empty(self):
models.Sleep.objects.all().delete() models.Sleep.objects.all().delete()
data = cards.card_sleep_last(self.context, self.child) data = cards.card_sleep_last(self.context, self.child)
self.assertEqual(data['type'], 'sleep') self.assertEqual(data["type"], "sleep")
self.assertTrue(data['empty']) self.assertTrue(data["empty"])
self.assertFalse(data['hide_empty']) self.assertFalse(data["hide_empty"])
def test_card_sleep_day(self): def test_card_sleep_day(self):
data = cards.card_sleep_day(self.context, self.child, self.date) data = cards.card_sleep_day(self.context, self.child, self.date)
self.assertEqual(data['type'], 'sleep') self.assertEqual(data["type"], "sleep")
self.assertFalse(data['empty']) self.assertFalse(data["empty"])
self.assertFalse(data['hide_empty']) self.assertFalse(data["hide_empty"])
self.assertEqual(data['total'], timezone.timedelta(2, 7200)) self.assertEqual(data["total"], timezone.timedelta(2, 7200))
self.assertEqual(data['count'], 4) self.assertEqual(data["count"], 4)
def test_card_sleep_naps_day(self): def test_card_sleep_naps_day(self):
data = cards.card_sleep_naps_day(self.context, self.child, self.date) data = cards.card_sleep_naps_day(self.context, self.child, self.date)
self.assertEqual(data['type'], 'sleep') self.assertEqual(data["type"], "sleep")
self.assertFalse(data['empty']) self.assertFalse(data["empty"])
self.assertFalse(data['hide_empty']) self.assertFalse(data["hide_empty"])
self.assertEqual(data['total'], timezone.timedelta(0, 9000)) self.assertEqual(data["total"], timezone.timedelta(0, 9000))
self.assertEqual(data['count'], 2) self.assertEqual(data["count"], 2)
def test_card_statistics(self): def test_card_statistics(self):
data = cards.card_statistics(self.context, self.child) data = cards.card_statistics(self.context, self.child)
stats = [ stats = [
{ {
'title': 'Diaper change frequency', "title": "Diaper change frequency",
'stat': timezone.timedelta(0, 44228, 571429), "stat": timezone.timedelta(0, 44228, 571429),
'type': 'duration' "type": "duration",
}, },
# Statistics date basis is not particularly strong to these feeding # Statistics date basis is not particularly strong to these feeding
# examples. # examples.
# TODO: Improve testing of feeding frequency statistics. # TODO: Improve testing of feeding frequency statistics.
{ {
'type': 'duration', "type": "duration",
'stat': 0.0, "stat": 0.0,
'title': 'Feeding frequency (past 3 days)' "title": "Feeding frequency (past 3 days)",
}, },
{ {
'type': 'duration', "type": "duration",
'stat': 0.0, "stat": 0.0,
'title': 'Feeding frequency (past 2 weeks)'}, "title": "Feeding frequency (past 2 weeks)",
{
'type': 'duration',
'stat': timezone.timedelta(0, 7200),
'title': 'Feeding frequency'
}, },
{ {
'title': 'Average nap duration', "type": "duration",
'stat': timezone.timedelta(0, 4500), "stat": timezone.timedelta(0, 7200),
'type': 'duration' "title": "Feeding frequency",
}, },
{ {
'title': 'Average naps per day', "title": "Average nap duration",
'stat': 2.0, "stat": timezone.timedelta(0, 4500),
'type': 'float' "type": "duration",
},
{"title": "Average naps per day", "stat": 2.0, "type": "float"},
{
"title": "Average sleep duration",
"stat": timezone.timedelta(0, 6750),
"type": "duration",
}, },
{ {
'title': 'Average sleep duration', "title": "Average awake duration",
'stat': timezone.timedelta(0, 6750), "stat": timezone.timedelta(0, 19200),
'type': 'duration' "type": "duration",
}, },
{"title": "Weight change per week", "stat": 1.0, "type": "float"},
{"title": "Height change per week", "stat": 1.0, "type": "float"},
{ {
'title': 'Average awake duration', "title": "Head circumference change per week",
'stat': timezone.timedelta(0, 19200), "stat": 1.0,
'type': 'duration' "type": "float",
}, },
{ {"title": "BMI change per week", "stat": 1.0, "type": "float"},
'title': 'Weight change per week',
'stat': 1.0, 'type':
'float'
},
{
'title': 'Height change per week',
'stat': 1.0, 'type':
'float'
},
{
'title': 'Head circumference change per week',
'stat': 1.0, 'type':
'float'
},
{
'title': 'BMI change per week',
'stat': 1.0, 'type':
'float'
}
] ]
self.assertEqual(data['stats'], stats) self.assertEqual(data["stats"], stats)
self.assertFalse(data['empty']) self.assertFalse(data["empty"])
self.assertFalse(data['hide_empty']) self.assertFalse(data["hide_empty"])
def test_card_timer_list(self): def test_card_timer_list(self):
user = User.objects.first() user = User.objects.first()
child = models.Child.objects.first() child = models.Child.objects.first()
child_two = models.Child.objects.create( child_two = models.Child.objects.create(
first_name='Child', first_name="Child", last_name="Two", birth_date=timezone.localdate()
last_name='Two',
birth_date=timezone.localdate()
) )
timers = { timers = {
'no_child': models.Timer.objects.create( "no_child": models.Timer.objects.create(
user=user, user=user, start=timezone.localtime() - timezone.timedelta(hours=3)
start=timezone.localtime() - timezone.timedelta(hours=3)
), ),
'child': models.Timer.objects.create( "child": models.Timer.objects.create(
user=user, user=user,
child=child, child=child,
start=timezone.localtime() - timezone.timedelta(hours=2) start=timezone.localtime() - timezone.timedelta(hours=2),
), ),
'child_two': models.Timer.objects.create( "child_two": models.Timer.objects.create(
user=user, user=user,
child=child_two, child=child_two,
start=timezone.localtime() - timezone.timedelta(hours=1) start=timezone.localtime() - timezone.timedelta(hours=1),
), ),
} }
data = cards.card_timer_list(self.context) data = cards.card_timer_list(self.context)
self.assertIsInstance(data['instances'][0], models.Timer) self.assertIsInstance(data["instances"][0], models.Timer)
self.assertEqual(len(data['instances']), 3) self.assertEqual(len(data["instances"]), 3)
data = cards.card_timer_list(self.context, child) data = cards.card_timer_list(self.context, child)
self.assertIsInstance(data['instances'][0], models.Timer) self.assertIsInstance(data["instances"][0], models.Timer)
self.assertTrue(timers['no_child'] in data['instances']) self.assertTrue(timers["no_child"] in data["instances"])
self.assertTrue(timers['child'] in data['instances']) self.assertTrue(timers["child"] in data["instances"])
self.assertFalse(timers['child_two'] in data['instances']) self.assertFalse(timers["child_two"] in data["instances"])
data = cards.card_timer_list(self.context, child_two) data = cards.card_timer_list(self.context, child_two)
self.assertIsInstance(data['instances'][0], models.Timer) self.assertIsInstance(data["instances"][0], models.Timer)
self.assertTrue(timers['no_child'] in data['instances']) self.assertTrue(timers["no_child"] in data["instances"])
self.assertTrue(timers['child_two'] in data['instances']) self.assertTrue(timers["child_two"] in data["instances"])
self.assertFalse(timers['child'] in data['instances']) self.assertFalse(timers["child"] in data["instances"])
def test_card_tummytime_last(self): def test_card_tummytime_last(self):
data = cards.card_tummytime_last(self.context, self.child) data = cards.card_tummytime_last(self.context, self.child)
self.assertEqual(data['type'], 'tummytime') self.assertEqual(data["type"], "tummytime")
self.assertFalse(data['empty']) self.assertFalse(data["empty"])
self.assertFalse(data['hide_empty']) self.assertFalse(data["hide_empty"])
self.assertIsInstance(data['tummytime'], models.TummyTime) self.assertIsInstance(data["tummytime"], models.TummyTime)
self.assertEqual(data['tummytime'], models.TummyTime.objects.first()) self.assertEqual(data["tummytime"], models.TummyTime.objects.first())
def test_card_tummytime_day(self): def test_card_tummytime_day(self):
data = cards.card_tummytime_day(self.context, self.child, self.date) data = cards.card_tummytime_day(self.context, self.child, self.date)
self.assertEqual(data['type'], 'tummytime') self.assertEqual(data["type"], "tummytime")
self.assertFalse(data['empty']) self.assertFalse(data["empty"])
self.assertFalse(data['hide_empty']) self.assertFalse(data["hide_empty"])
self.assertIsInstance(data['instances'].first(), models.TummyTime) self.assertIsInstance(data["instances"].first(), models.TummyTime)
self.assertIsInstance(data['last'], models.TummyTime) self.assertIsInstance(data["last"], models.TummyTime)
stats = {'count': 3, 'total': timezone.timedelta(0, 300)} stats = {"count": 3, "total": timezone.timedelta(0, 300)}
self.assertEqual(data['stats'], stats) self.assertEqual(data["stats"], stats)

View File

@ -14,42 +14,37 @@ class ViewsTestCase(TestCase):
def setUpClass(cls): def setUpClass(cls):
super(ViewsTestCase, cls).setUpClass() super(ViewsTestCase, cls).setUpClass()
fake = Factory.create() fake = Factory.create()
call_command('migrate', verbosity=0) call_command("migrate", verbosity=0)
cls.c = HttpClient() cls.c = HttpClient()
fake_user = fake.simple_profile() fake_user = fake.simple_profile()
cls.credentials = { cls.credentials = {
'username': fake_user['username'], "username": fake_user["username"],
'password': fake.password() "password": fake.password(),
} }
cls.user = User.objects.create_user( cls.user = User.objects.create_user(is_superuser=True, **cls.credentials)
is_superuser=True, **cls.credentials)
cls.c.login(**cls.credentials) cls.c.login(**cls.credentials)
def test_dashboard_views(self): def test_dashboard_views(self):
page = self.c.get('/dashboard/') page = self.c.get("/dashboard/")
self.assertEqual(page.url, '/welcome/') self.assertEqual(page.url, "/welcome/")
call_command('fake', verbosity=0, children=1, days=1) call_command("fake", verbosity=0, children=1, days=1)
child = Child.objects.first() child = Child.objects.first()
page = self.c.get('/dashboard/') page = self.c.get("/dashboard/")
self.assertEqual( self.assertEqual(page.url, "/children/{}/dashboard/".format(child.slug))
page.url, '/children/{}/dashboard/'.format(child.slug))
page = self.c.get('/dashboard/') page = self.c.get("/dashboard/")
self.assertEqual( self.assertEqual(page.url, "/children/{}/dashboard/".format(child.slug))
page.url, '/children/{}/dashboard/'.format(child.slug))
# Test the actual child dashboard (including cards). # Test the actual child dashboard (including cards).
# TODO: Test cards more granularly. # TODO: Test cards more granularly.
page = self.c.get('/children/{}/dashboard/'.format(child.slug)) page = self.c.get("/children/{}/dashboard/".format(child.slug))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
Child.objects.create( Child.objects.create(
first_name='Second', first_name="Second", last_name="Child", birth_date="2000-01-01"
last_name='Child',
birth_date='2000-01-01'
) )
page = self.c.get('/dashboard/') page = self.c.get("/dashboard/")
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)

View File

@ -3,13 +3,13 @@ from django.urls import path
from . import views from . import views
app_name = 'dashboard' app_name = "dashboard"
urlpatterns = [ urlpatterns = [
path('dashboard/', views.Dashboard.as_view(), name='dashboard'), path("dashboard/", views.Dashboard.as_view(), name="dashboard"),
path( path(
'children/<str:slug>/dashboard/', "children/<str:slug>/dashboard/",
views.ChildDashboard.as_view(), views.ChildDashboard.as_view(),
name='dashboard-child' name="dashboard-child",
), ),
] ]

View File

@ -10,30 +10,28 @@ from core.models import Child
class Dashboard(LoginRequiredMixin, TemplateView): class Dashboard(LoginRequiredMixin, TemplateView):
# TODO: Use .card-deck in this template once BS4 is finalized. # TODO: Use .card-deck in this template once BS4 is finalized.
template_name = 'dashboard/dashboard.html' template_name = "dashboard/dashboard.html"
# Show the overall dashboard or a child dashboard if one Child instance. # Show the overall dashboard or a child dashboard if one Child instance.
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
children = Child.objects.count() children = Child.objects.count()
if children == 0: if children == 0:
return HttpResponseRedirect(reverse('babybuddy:welcome')) return HttpResponseRedirect(reverse("babybuddy:welcome"))
elif children == 1: elif children == 1:
return HttpResponseRedirect( return HttpResponseRedirect(
reverse( reverse("dashboard:dashboard-child", args={Child.objects.first().slug})
'dashboard:dashboard-child',
args={Child.objects.first().slug}
)
) )
return super(Dashboard, self).get(request, *args, **kwargs) return super(Dashboard, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(Dashboard, self).get_context_data(**kwargs) context = super(Dashboard, self).get_context_data(**kwargs)
context['objects'] = Child.objects.all() \ context["objects"] = Child.objects.all().order_by(
.order_by('last_name', 'first_name', 'id') "last_name", "first_name", "id"
)
return context return context
class ChildDashboard(PermissionRequiredMixin, DetailView): class ChildDashboard(PermissionRequiredMixin, DetailView):
model = Child model = Child
permission_required = ('core.view_child',) permission_required = ("core.view_child",)
template_name = 'dashboard/child.html' template_name = "dashboard/child.html"

View File

@ -1,5 +1,5 @@
# Server mechanics # Server mechanics
bind = '0.0.0.0:8000' bind = "0.0.0.0:8000"
backlog = 2048 backlog = 2048
daemon = False daemon = False
pidfile = None pidfile = None
@ -10,9 +10,9 @@ tmp_upload_dir = None
proc_name = None proc_name = None
# Logging # Logging
errorlog = '-' errorlog = "-"
loglevel = 'info' loglevel = "info"
accesslog = '-' accesslog = "-"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
# #
@ -65,7 +65,7 @@ access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"
# #
workers = 1 workers = 1
worker_class = 'sync' worker_class = "sync"
worker_connections = 1000 worker_connections = 1000
timeout = 30 timeout = 30
keepalive = 2 keepalive = 2
@ -112,14 +112,13 @@ def worker_int(worker):
# get traceback info # get traceback info
import threading, sys, traceback import threading, sys, traceback
id2name = dict([(th.ident, th.name) for th in threading.enumerate()]) id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
code = [] code = []
for threadId, stack in sys._current_frames().items(): for threadId, stack in sys._current_frames().items():
code.append("\n# Thread: %s(%d)" % (id2name.get(threadId,""), code.append("\n# Thread: %s(%d)" % (id2name.get(threadId, ""), threadId))
threadId))
for filename, lineno, name, line in traceback.extract_stack(stack): for filename, lineno, name, line in traceback.extract_stack(stack):
code.append('File: "%s", line %d, in %s' % (filename, code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
lineno, name))
if line: if line:
code.append(" %s" % (line.strip())) code.append(" %s" % (line.strip()))
worker.log.debug("\n".join(code)) worker.log.debug("\n".join(code))

View File

@ -8,5 +8,7 @@ from .sleep_totals import sleep_totals # NOQA
from .tummytime_duration import tummytime_duration # NOQA from .tummytime_duration import tummytime_duration # NOQA
from .weight_weight import weight_weight # NOQA from .weight_weight import weight_weight # NOQA
from .height_height import height_height # NOQA from .height_height import height_height # NOQA
from .head_circumference_head_circumference import head_circumference_head_circumference # NOQA from .head_circumference_head_circumference import (
head_circumference_head_circumference,
) # NOQA
from .bmi_bmi import bmi_bmi # NOQA from .bmi_bmi import bmi_bmi # NOQA

View File

@ -13,25 +13,22 @@ def bmi_bmi(objects):
:param objects: a QuerySet of BMI instances. :param objects: a QuerySet of BMI instances.
:returns: a tuple of the the graph's html and javascript. :returns: a tuple of the the graph's html and javascript.
""" """
objects = objects.order_by('-date') objects = objects.order_by("-date")
trace = go.Scatter( trace = go.Scatter(
name=_('BMI'), name=_("BMI"),
x=list(objects.values_list('date', flat=True)), x=list(objects.values_list("date", flat=True)),
y=list(objects.values_list('bmi', flat=True)), y=list(objects.values_list("bmi", flat=True)),
fill='tozeroy', fill="tozeroy",
) )
layout_args = utils.default_graph_layout_options() layout_args = utils.default_graph_layout_options()
layout_args['barmode'] = 'stack' layout_args["barmode"] = "stack"
layout_args['title'] = _('<b>BMI</b>') layout_args["title"] = _("<b>BMI</b>")
layout_args['xaxis']['title'] = _('Date') layout_args["xaxis"]["title"] = _("Date")
layout_args['xaxis']['rangeselector'] = utils.rangeselector_date() layout_args["xaxis"]["rangeselector"] = utils.rangeselector_date()
layout_args['yaxis']['title'] = _('BMI') layout_args["yaxis"]["title"] = _("BMI")
fig = go.Figure({ fig = go.Figure({"data": [trace], "layout": go.Layout(**layout_args)})
'data': [trace], output = plotly.plot(fig, output_type="div", include_plotlyjs=False)
'layout': go.Layout(**layout_args)
})
output = plotly.plot(fig, output_type='div', include_plotlyjs=False)
return utils.split_graph_output(output) return utils.split_graph_output(output)

View File

@ -24,23 +24,20 @@ def diaperchange_amounts(instances):
amounts = [round(amount, 2) for amount in totals.values()] amounts = [round(amount, 2) for amount in totals.values()]
trace = go.Bar( trace = go.Bar(
name=_('Diaper change amount'), name=_("Diaper change amount"),
x=list(totals.keys()), x=list(totals.keys()),
y=amounts, y=amounts,
hoverinfo='text', hoverinfo="text",
textposition='outside', textposition="outside",
text=amounts text=amounts,
) )
layout_args = utils.default_graph_layout_options() layout_args = utils.default_graph_layout_options()
layout_args['title'] = _('<b>Diaper Change Amounts</b>') layout_args["title"] = _("<b>Diaper Change Amounts</b>")
layout_args['xaxis']['title'] = _('Date') layout_args["xaxis"]["title"] = _("Date")
layout_args['xaxis']['rangeselector'] = utils.rangeselector_date() layout_args["xaxis"]["rangeselector"] = utils.rangeselector_date()
layout_args['yaxis']['title'] = _('Change amount') layout_args["yaxis"]["title"] = _("Change amount")
fig = go.Figure({ fig = go.Figure({"data": [trace], "layout": go.Layout(**layout_args)})
'data': [trace], output = plotly.plot(fig, output_type="div", include_plotlyjs=False)
'layout': go.Layout(**layout_args)
})
output = plotly.plot(fig, output_type='div', include_plotlyjs=False)
return utils.split_graph_output(output) return utils.split_graph_output(output)

View File

@ -13,7 +13,7 @@ def diaperchange_lifetimes(changes):
:param changes: a QuerySet of Diaper Change instances. :param changes: a QuerySet of Diaper Change instances.
:returns: a tuple of the the graph's html and javascript. :returns: a tuple of the the graph's html and javascript.
""" """
changes = changes.order_by('time') changes = changes.order_by("time")
durations = [] durations = []
last_change = changes.first() last_change = changes.first()
for change in changes[1:]: for change in changes[1:]:
@ -24,22 +24,19 @@ def diaperchange_lifetimes(changes):
trace = go.Box( trace = go.Box(
y=[round(d.seconds / 3600, 2) for d in durations], y=[round(d.seconds / 3600, 2) for d in durations],
name=_('Changes'), name=_("Changes"),
jitter=0.3, jitter=0.3,
pointpos=-1.8, pointpos=-1.8,
boxpoints='all' boxpoints="all",
) )
layout_args = utils.default_graph_layout_options() layout_args = utils.default_graph_layout_options()
layout_args['height'] = 800 layout_args["height"] = 800
layout_args['title'] = _('<b>Diaper Lifetimes</b>') layout_args["title"] = _("<b>Diaper Lifetimes</b>")
layout_args['yaxis']['title'] = _('Time between changes (hours)') layout_args["yaxis"]["title"] = _("Time between changes (hours)")
layout_args['yaxis']['zeroline'] = False layout_args["yaxis"]["zeroline"] = False
layout_args['yaxis']['dtick'] = 1 layout_args["yaxis"]["dtick"] = 1
fig = go.Figure({ fig = go.Figure({"data": [trace], "layout": go.Layout(**layout_args)})
'data': [trace], output = plotly.plot(fig, output_type="div", include_plotlyjs=False)
'layout': go.Layout(**layout_args)
})
output = plotly.plot(fig, output_type='div', include_plotlyjs=False)
return utils.split_graph_output(output) return utils.split_graph_output(output)

View File

@ -16,46 +16,50 @@ def diaperchange_types(changes):
:param changes: a QuerySet of Diaper Change instances. :param changes: a QuerySet of Diaper Change instances.
:returns: a tuple of the the graph's html and javascript. :returns: a tuple of the the graph's html and javascript.
""" """
changes = changes.annotate(date=TruncDate('time'))\ changes = (
.values('date') \ changes.annotate(date=TruncDate("time"))
.annotate(wet_count=Count(Case(When(wet=True, then=1)))) \ .values("date")
.annotate(solid_count=Count(Case(When(solid=True, then=1)))) \ .annotate(wet_count=Count(Case(When(wet=True, then=1))))
.annotate(total=Count('id')) \ .annotate(solid_count=Count(Case(When(solid=True, then=1))))
.order_by('-date') .annotate(total=Count("id"))
.order_by("-date")
)
solid_trace = go.Scatter( solid_trace = go.Scatter(
mode='markers', mode="markers",
name=_('Solid'), name=_("Solid"),
x=list(changes.values_list('date', flat=True)), x=list(changes.values_list("date", flat=True)),
y=list(changes.values_list('solid_count', flat=True)), y=list(changes.values_list("solid_count", flat=True)),
) )
wet_trace = go.Scatter( wet_trace = go.Scatter(
mode='markers', mode="markers",
name=_('Wet'), name=_("Wet"),
x=list(changes.values_list('date', flat=True)), x=list(changes.values_list("date", flat=True)),
y=list(changes.values_list('wet_count', flat=True)) y=list(changes.values_list("wet_count", flat=True)),
) )
total_trace = go.Scatter( total_trace = go.Scatter(
name=_('Total'), name=_("Total"),
x=list(changes.values_list('date', flat=True)), x=list(changes.values_list("date", flat=True)),
y=list(changes.values_list('total', flat=True)) y=list(changes.values_list("total", flat=True)),
) )
layout_args = utils.default_graph_layout_options() layout_args = utils.default_graph_layout_options()
layout_args['barmode'] = 'stack' layout_args["barmode"] = "stack"
layout_args['title'] = _('<b>Diaper Change Types</b>') layout_args["title"] = _("<b>Diaper Change Types</b>")
layout_args['xaxis']['title'] = _('Date') layout_args["xaxis"]["title"] = _("Date")
layout_args['xaxis']['rangeselector'] = utils.rangeselector_date() layout_args["xaxis"]["rangeselector"] = utils.rangeselector_date()
layout_args['yaxis']['title'] = _('Number of changes') layout_args["yaxis"]["title"] = _("Number of changes")
fig = go.Figure({ fig = go.Figure(
'data': [solid_trace, wet_trace, total_trace], {
'layout': go.Layout(**layout_args) "data": [solid_trace, wet_trace, total_trace],
}) "layout": go.Layout(**layout_args),
}
)
output = plotly.plot( output = plotly.plot(
fig, fig,
output_type='div', output_type="div",
include_plotlyjs=False, include_plotlyjs=False,
config={'locale': get_language()} config={"locale": get_language()},
) )
return utils.split_graph_output(output) return utils.split_graph_output(output)

Some files were not shown because too many files have changed in this diff Show More