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

View File

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

View File

@ -11,17 +11,19 @@ class TimerFieldSupportMixin:
"""
meta = self.metadata_class()
data = meta.determine_metadata(request, self)
post = data.get('actions').get('POST') # type: OrderedDict
post['timer'] = OrderedDict({
post = data.get("actions").get("POST") # type: OrderedDict
post["timer"] = OrderedDict(
{
"type": "integer",
"required": False,
"read_only": False,
"label": "Timer",
"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."
post['child']['details'] = details
post['start']['details'] = details
post['end']['details'] = details
post["child"]["details"] = details
post["start"]["details"] = details
post["end"]["details"] = details
return Response(data)

View File

@ -4,11 +4,11 @@ from rest_framework.permissions import DjangoModelPermissions
class BabyBuddyDjangoModelPermissions(DjangoModelPermissions):
perms_map = {
'GET': ['%(app_label)s.view_%(model_name)s'],
'OPTIONS': ['%(app_label)s.add_%(model_name)s'],
'HEAD': [],
'POST': ['%(app_label)s.add_%(model_name)s'],
"GET": ["%(app_label)s.view_%(model_name)s"],
"OPTIONS": ["%(app_label)s.add_%(model_name)s"],
"HEAD": [],
"POST": ["%(app_label)s.add_%(model_name)s"],
# 'PUT': ['%(app_label)s.change_%(model_name)s'],
'PATCH': ['%(app_label)s.change_%(model_name)s'],
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
"PATCH": ["%(app_label)s.change_%(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()
methods during POST operations.
"""
child = serializers.PrimaryKeyRelatedField(
queryset=models.Child.objects.all())
child = serializers.PrimaryKeyRelatedField(queryset=models.Child.objects.all())
def validate(self, attrs):
# 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.
"""
child = serializers.PrimaryKeyRelatedField(
allow_null=True, allow_empty=True, queryset=models.Child.objects.all(),
required=False)
allow_null=True,
allow_empty=True,
queryset=models.Child.objects.all(),
required=False,
)
class Meta:
abstract = True
extra_kwargs = {
'start': {'required': False},
'end': {'required': False},
"start": {"required": False},
"end": {"required": False},
}
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
# Timer entry.
timer = None
if 'timer' in self.initial_data:
if "timer" in self.initial_data:
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:
raise ValidationError({'timer': ['Timer does not exist.']})
raise ValidationError({"timer": ["Timer does not exist."]})
if timer.end:
end = timer.end
else:
end = timezone.now()
if timer.child:
attrs['child'] = timer.child
attrs["child"] = timer.child
# Overwrites values provided directly!
attrs['start'] = timer.start
attrs['end'] = end
attrs["start"] = timer.start
attrs["end"] = end
# The "child", "start", and "end" field should all be set at this
# point. If one is not, model validation will fail because they are
# required fields at the model level.
if not self.partial:
errors = {}
for field in ['child', 'start', 'end']:
for field in ["child", "start", "end"]:
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:
raise ValidationError(errors)
@ -81,7 +85,7 @@ class CoreModelWithDurationSerializer(CoreModelSerializer):
# Only actually stop the timer if all validation passed.
if timer:
timer.stop(attrs['end'])
timer.stop(attrs["end"])
return attrs
@ -89,76 +93,77 @@ class CoreModelWithDurationSerializer(CoreModelSerializer):
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username')
fields = ("id", "username")
class ChildSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = models.Child
fields = ('id', 'first_name', 'last_name', 'birth_date', 'slug',
'picture')
lookup_field = 'slug'
fields = ("id", "first_name", "last_name", "birth_date", "slug", "picture")
lookup_field = "slug"
class DiaperChangeSerializer(CoreModelSerializer):
class Meta:
model = models.DiaperChange
fields = (
'id',
'child',
'time',
'wet',
'solid',
'color',
'amount',
'notes'
)
fields = ("id", "child", "time", "wet", "solid", "color", "amount", "notes")
class FeedingSerializer(CoreModelWithDurationSerializer):
class Meta(CoreModelWithDurationSerializer.Meta):
model = models.Feeding
fields = ('id', 'child', 'start', 'end', 'duration', 'type', 'method',
'amount', 'notes')
fields = (
"id",
"child",
"start",
"end",
"duration",
"type",
"method",
"amount",
"notes",
)
class NoteSerializer(CoreModelSerializer):
class Meta:
model = models.Note
fields = ('id', 'child', 'note', 'time')
fields = ("id", "child", "note", "time")
class SleepSerializer(CoreModelWithDurationSerializer):
class Meta(CoreModelWithDurationSerializer.Meta):
model = models.Sleep
fields = ('id', 'child', 'start', 'end', 'duration', 'nap', 'notes')
fields = ("id", "child", "start", "end", "duration", "nap", "notes")
class TemperatureSerializer(CoreModelSerializer):
class Meta:
model = models.Temperature
fields = ('id', 'child', 'temperature', 'time', 'notes')
fields = ("id", "child", "temperature", "time", "notes")
class TimerSerializer(CoreModelSerializer):
child = serializers.PrimaryKeyRelatedField(
allow_null=True, allow_empty=True, queryset=models.Child.objects.all(),
required=False)
allow_null=True,
allow_empty=True,
queryset=models.Child.objects.all(),
required=False,
)
user = serializers.PrimaryKeyRelatedField(
allow_null=True, allow_empty=True, queryset=User.objects.all(),
required=False)
allow_null=True, allow_empty=True, queryset=User.objects.all(), required=False
)
class Meta:
model = models.Timer
fields = ('id', 'child', 'name', 'start', 'end', 'duration', 'active',
'user')
fields = ("id", "child", "name", "start", "end", "duration", "active", "user")
def validate(self, attrs):
attrs = super(TimerSerializer, self).validate(attrs)
# Set user to current user if no value is provided.
if 'user' not in attrs or attrs['user'] is None:
attrs['user'] = self.context['request'].user
if "user" not in attrs or attrs["user"] is None:
attrs["user"] = self.context["request"].user
return attrs
@ -166,28 +171,28 @@ class TimerSerializer(CoreModelSerializer):
class TummyTimeSerializer(CoreModelWithDurationSerializer):
class Meta(CoreModelWithDurationSerializer.Meta):
model = models.TummyTime
fields = ('id', 'child', 'start', 'end', 'duration', 'milestone')
fields = ("id", "child", "start", "end", "duration", "milestone")
class WeightSerializer(CoreModelSerializer):
class Meta:
model = models.Weight
fields = ('id', 'child', 'weight', 'date', 'notes')
fields = ("id", "child", "weight", "date", "notes")
class HeightSerializer(CoreModelSerializer):
class Meta:
model = models.Height
fields = ('id', 'child', 'height', 'date', 'notes')
fields = ("id", "child", "height", "date", "notes")
class HeadCircumferenceSerializer(CoreModelSerializer):
class Meta:
model = models.HeadCircumference
fields = ('id', 'child', 'head_circumference', 'date', 'notes')
fields = ("id", "child", "head_circumference", "date", "notes")
class BMISerializer(CoreModelSerializer):
class Meta:
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 BabyBuddyAPITestCaseBase(APITestCase):
fixtures = ['tests.json']
fixtures = ["tests.json"]
model = None
endpoint = None
delete_id = 1
timer_test_data = {}
def setUp(self):
self.client.login(username='admin', password='admin')
self.client.login(username="admin", password="admin")
def test_options(self):
response = self.client.options(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['name'], '{} List'.format(
self.model._meta.verbose_name))
self.assertEqual(
response.data["name"], "{} List".format(self.model._meta.verbose_name)
)
def test_delete(self):
endpoint = '{}{}/'.format(self.endpoint, self.delete_id)
endpoint = "{}{}/".format(self.endpoint, self.delete_id)
response = self.client.get(endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response = self.client.delete(endpoint)
@ -40,24 +40,26 @@ class TestBase:
user = User.objects.first()
start = timezone.now() - timezone.timedelta(minutes=10)
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:
del self.timer_test_data['child']
if "child" in self.timer_test_data:
del self.timer_test_data["child"]
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)
timer.refresh_from_db()
self.assertTrue(timer.active)
child = models.Child.objects.first()
self.timer_test_data['child'] = child.id
self.timer_test_data["child"] = child.id
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)
timer.refresh_from_db()
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.end, timer.end)
@ -67,339 +69,374 @@ class TestBase:
user = User.objects.first()
child = models.Child.objects.first()
start = timezone.now() - timezone.timedelta(minutes=10)
timer = models.Timer.objects.create(
user=user, child=child, start=start)
self.timer_test_data['timer'] = timer.id
timer = models.Timer.objects.create(user=user, child=child, start=start)
self.timer_test_data["timer"] = timer.id
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)
timer.refresh_from_db()
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.start, start)
self.assertEqual(obj.end, timer.end)
class ChildAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
endpoint = reverse('api:child-list')
endpoint = reverse("api:child-list")
model = models.Child
delete_id = 'fake-child'
delete_id = "fake-child"
def test_get(self):
response = self.client.get(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'][0], {
'id': 1,
'first_name': 'Fake',
'last_name': 'Child',
'birth_date': '2017-11-11',
'slug': 'fake-child',
'picture': None
})
self.assertEqual(
response.data["results"][0],
{
"id": 1,
"first_name": "Fake",
"last_name": "Child",
"birth_date": "2017-11-11",
"slug": "fake-child",
"picture": None,
},
)
def test_post(self):
data = {
'first_name': 'Test',
'last_name': 'Child',
'birth_date': '2017-11-12'
}
response = self.client.post(self.endpoint, data, format='json')
data = {"first_name": "Test", "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)
obj = models.Child.objects.get(pk=response.data['id'])
self.assertEqual(obj.first_name, data['first_name'])
obj = models.Child.objects.get(pk=response.data["id"])
self.assertEqual(obj.first_name, data["first_name"])
def test_patch(self):
endpoint = '{}{}/'.format(self.endpoint, 'fake-child')
endpoint = "{}{}/".format(self.endpoint, "fake-child")
response = self.client.get(endpoint)
entry = response.data
entry['first_name'] = 'New'
entry['last_name'] = 'Name'
response = self.client.patch(endpoint, {
'first_name': entry['first_name'],
'last_name': entry['last_name'],
})
entry["first_name"] = "New"
entry["last_name"] = "Name"
response = self.client.patch(
endpoint,
{
"first_name": entry["first_name"],
"last_name": entry["last_name"],
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# The slug we be updated by the name change.
entry['slug'] = 'new-name'
entry["slug"] = "new-name"
self.assertEqual(response.data, entry)
class DiaperChangeAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
endpoint = reverse('api:diaperchange-list')
endpoint = reverse("api:diaperchange-list")
model = models.DiaperChange
delete_id = 3
def test_get(self):
response = self.client.get(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'][0], {
'id': 3,
'child': 1,
'time': '2017-11-18T14:00:00-05:00',
'wet': True,
'solid': False,
'color': '',
'amount': 2.25,
'notes': 'stinky'
})
self.assertEqual(
response.data["results"][0],
{
"id": 3,
"child": 1,
"time": "2017-11-18T14:00:00-05:00",
"wet": True,
"solid": False,
"color": "",
"amount": 2.25,
"notes": "stinky",
},
)
def test_post(self):
data = {
'child': 1,
'time': '2017-11-18T12:00:00-05:00',
'wet': True,
'solid': True,
'color': 'brown',
'amount': 1.25,
'notes': 'seedy'
"child": 1,
"time": "2017-11-18T12:00:00-05:00",
"wet": True,
"solid": True,
"color": "brown",
"amount": 1.25,
"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)
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.solid)
self.assertEqual(obj.color, data['color'])
self.assertEqual(obj.amount, data['amount'])
self.assertEqual(obj.notes, data['notes'])
self.assertEqual(obj.color, data["color"])
self.assertEqual(obj.amount, data["amount"])
self.assertEqual(obj.notes, data["notes"])
def test_patch(self):
endpoint = '{}{}/'.format(self.endpoint, 3)
endpoint = "{}{}/".format(self.endpoint, 3)
response = self.client.get(endpoint)
entry = response.data
entry['wet'] = False
entry['solid'] = True
response = self.client.patch(endpoint, {
'wet': entry['wet'],
'solid': entry['solid'],
})
entry["wet"] = False
entry["solid"] = True
response = self.client.patch(
endpoint,
{
"wet": entry["wet"],
"solid": entry["solid"],
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, entry)
class FeedingAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
endpoint = reverse('api:feeding-list')
endpoint = reverse("api:feeding-list")
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):
response = self.client.get(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'][0], {
'id': 3,
'child': 1,
'start': '2017-11-18T14:00:00-05:00',
'end': '2017-11-18T14:15:00-05:00',
'duration': '00:15:00',
'type': 'formula',
'method': 'bottle',
'amount': 2.5,
'notes': 'forgot vitamins :('
})
self.assertEqual(
response.data["results"][0],
{
"id": 3,
"child": 1,
"start": "2017-11-18T14:00:00-05:00",
"end": "2017-11-18T14:15:00-05:00",
"duration": "00:15:00",
"type": "formula",
"method": "bottle",
"amount": 2.5,
"notes": "forgot vitamins :(",
},
)
def test_post(self):
data = {
'child': 1,
'start': '2017-11-19T14:00:00-05:00',
'end': '2017-11-19T14:15:00-05:00',
'type': 'breast milk',
'method': 'left breast',
'notes': 'with vitamins'
"child": 1,
"start": "2017-11-19T14:00:00-05:00",
"end": "2017-11-19T14:15:00-05:00",
"type": "breast milk",
"method": "left breast",
"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)
obj = models.Feeding.objects.get(pk=response.data['id'])
self.assertEqual(obj.type, data['type'])
self.assertEqual(obj.notes, data['notes'])
obj = models.Feeding.objects.get(pk=response.data["id"])
self.assertEqual(obj.type, data["type"])
self.assertEqual(obj.notes, data["notes"])
def test_patch(self):
endpoint = '{}{}/'.format(self.endpoint, 3)
endpoint = "{}{}/".format(self.endpoint, 3)
response = self.client.get(endpoint)
entry = response.data
entry['type'] = 'breast milk'
entry['method'] = 'left breast'
entry['amount'] = 0
response = self.client.patch(endpoint, {
'type': entry['type'],
'method': entry['method'],
'amount': entry['amount'],
})
entry["type"] = "breast milk"
entry["method"] = "left breast"
entry["amount"] = 0
response = self.client.patch(
endpoint,
{
"type": entry["type"],
"method": entry["method"],
"amount": entry["amount"],
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, entry)
class NoteAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
endpoint = reverse('api:note-list')
endpoint = reverse("api:note-list")
model = models.Note
def test_get(self):
response = self.client.get(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'][0], {
'id': 1,
'child': 1,
'note': 'Fake note.',
'time': '2017-11-17T22:45:00-05:00'
})
self.assertEqual(
response.data["results"][0],
{
"id": 1,
"child": 1,
"note": "Fake note.",
"time": "2017-11-17T22:45:00-05:00",
},
)
def test_post(self):
data = {
'child': 1,
'note': 'New fake note.',
'time': '2017-11-18T22:45:00-05:00'
"child": 1,
"note": "New fake note.",
"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)
obj = models.Note.objects.get(pk=response.data['id'])
self.assertEqual(obj.note, data['note'])
obj = models.Note.objects.get(pk=response.data["id"])
self.assertEqual(obj.note, data["note"])
def test_patch(self):
endpoint = '{}{}/'.format(self.endpoint, 1)
endpoint = "{}{}/".format(self.endpoint, 1)
response = self.client.get(endpoint)
entry = response.data
entry['note'] = 'Updated note text.'
response = self.client.patch(endpoint, {
'note': entry['note'],
})
entry["note"] = "Updated note text."
response = self.client.patch(
endpoint,
{
"note": entry["note"],
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# The time of entry will always update automatically, so only check the
# new value.
self.assertEqual(response.data['note'], entry['note'])
self.assertEqual(response.data["note"], entry["note"])
class SleepAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
endpoint = reverse('api:sleep-list')
endpoint = reverse("api:sleep-list")
model = models.Sleep
timer_test_data = {'child': 1}
timer_test_data = {"child": 1}
def test_get(self):
response = self.client.get(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'][0], {
'id': 4,
'child': 1,
'start': '2017-11-18T19:00:00-05:00',
'end': '2017-11-18T23:00:00-05:00',
'duration': '04:00:00',
'nap': False,
'notes': 'lots of squirming'
})
self.assertEqual(
response.data["results"][0],
{
"id": 4,
"child": 1,
"start": "2017-11-18T19:00:00-05:00",
"end": "2017-11-18T23:00:00-05:00",
"duration": "04:00:00",
"nap": False,
"notes": "lots of squirming",
},
)
def test_post(self):
data = {
'child': 1,
'start': '2017-11-21T19:30:00-05:00',
'end': '2017-11-21T23:00:00-05:00',
'notes': 'used new swaddle'
"child": 1,
"start": "2017-11-21T19:30:00-05:00",
"end": "2017-11-21T23:00:00-05:00",
"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)
obj = models.Sleep.objects.get(pk=response.data['id'])
self.assertEqual(str(obj.duration), '3:30:00')
self.assertEqual(obj.notes, data['notes'])
obj = models.Sleep.objects.get(pk=response.data["id"])
self.assertEqual(str(obj.duration), "3:30:00")
self.assertEqual(obj.notes, data["notes"])
def test_patch(self):
endpoint = '{}{}/'.format(self.endpoint, 4)
endpoint = "{}{}/".format(self.endpoint, 4)
response = self.client.get(endpoint)
entry = response.data
entry['end'] = '2017-11-18T23:30:00-05:00'
response = self.client.patch(endpoint, {
'end': entry['end'],
})
entry["end"] = "2017-11-18T23:30:00-05:00"
response = self.client.patch(
endpoint,
{
"end": entry["end"],
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# The duration of entry will always update automatically, so only check
# the new value.
self.assertEqual(response.data['end'], entry['end'])
self.assertEqual(response.data["end"], entry["end"])
class TemperatureAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
endpoint = reverse('api:temperature-list')
endpoint = reverse("api:temperature-list")
model = models.Temperature
def test_get(self):
response = self.client.get(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'][0], {
'id': 1,
'child': 1,
'temperature': 98.6,
'time': '2017-11-17T12:52:00-05:00',
'notes': 'tympanic'
})
self.assertEqual(
response.data["results"][0],
{
"id": 1,
"child": 1,
"temperature": 98.6,
"time": "2017-11-17T12:52:00-05:00",
"notes": "tympanic",
},
)
def test_post(self):
data = {
'child': 1,
'temperature': '100.1',
'time': '2017-11-20T22:52:00-05:00',
'notes': 'rectal'
"child": 1,
"temperature": "100.1",
"time": "2017-11-20T22:52:00-05:00",
"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)
obj = models.Temperature.objects.get(pk=response.data['id'])
self.assertEqual(str(obj.temperature), data['temperature'])
self.assertEqual(obj.notes, data['notes'])
obj = models.Temperature.objects.get(pk=response.data["id"])
self.assertEqual(str(obj.temperature), data["temperature"])
self.assertEqual(obj.notes, data["notes"])
def test_patch(self):
endpoint = '{}{}/'.format(self.endpoint, 1)
endpoint = "{}{}/".format(self.endpoint, 1)
response = self.client.get(endpoint)
entry = response.data
entry['temperature'] = 99
response = self.client.patch(endpoint, {
'temperature': entry['temperature'],
})
entry["temperature"] = 99
response = self.client.patch(
endpoint,
{
"temperature": entry["temperature"],
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, entry)
class TimerAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
endpoint = reverse('api:timer-list')
endpoint = reverse("api:timer-list")
model = models.Timer
def test_get(self):
response = self.client.get(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'][0], {
'id': 1,
'child': None,
'name': 'Fake timer',
'start': '2017-11-17T23:30:00-05:00',
'end': '2017-11-18T00:30:00-05:00',
'duration': '01:00:00',
'active': False,
'user': 1
})
self.assertEqual(
response.data["results"][0],
{
"id": 1,
"child": None,
"name": "Fake timer",
"start": "2017-11-17T23:30:00-05:00",
"end": "2017-11-18T00:30:00-05:00",
"duration": "01:00:00",
"active": False,
"user": 1,
},
)
def test_post(self):
data = {
'name': 'New fake timer',
'user': 1
}
response = self.client.post(self.endpoint, data, format='json')
data = {"name": "New fake timer", "user": 1}
response = self.client.post(self.endpoint, data, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
obj = models.Timer.objects.get(pk=response.data['id'])
self.assertEqual(obj.name, data['name'])
obj = models.Timer.objects.get(pk=response.data["id"])
self.assertEqual(obj.name, data["name"])
def test_post_default_user(self):
user = User.objects.first()
response = self.client.post(self.endpoint)
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)
def test_patch(self):
endpoint = '{}{}/'.format(self.endpoint, 1)
endpoint = "{}{}/".format(self.endpoint, 1)
response = self.client.get(endpoint)
entry = response.data
entry['name'] = 'New Timer Name'
response = self.client.patch(endpoint, {
'name': entry['name'],
})
entry["name"] = "New Timer Name"
response = self.client.patch(
endpoint,
{
"name": entry["name"],
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, entry)
def test_start_stop_timer(self):
endpoint = '{}{}/'.format(self.endpoint, 1)
endpoint = "{}{}/".format(self.endpoint, 1)
response = self.client.get(endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertFalse(response.data["active"])
@ -424,81 +461,93 @@ class TimerAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
class TummyTimeAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
endpoint = reverse('api:tummytime-list')
endpoint = reverse("api:tummytime-list")
model = models.TummyTime
timer_test_data = {'milestone': 'Timer test'}
timer_test_data = {"milestone": "Timer test"}
def test_get(self):
response = self.client.get(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'][0], {
'id': 3,
'child': 1,
'start': '2017-11-18T15:30:00-05:00',
'end': '2017-11-18T15:30:45-05:00',
'duration': '00:00:45',
'milestone': ''
})
self.assertEqual(
response.data["results"][0],
{
"id": 3,
"child": 1,
"start": "2017-11-18T15:30:00-05:00",
"end": "2017-11-18T15:30:45-05:00",
"duration": "00:00:45",
"milestone": "",
},
)
def test_post(self):
data = {
'child': 1,
'start': '2017-11-18T12:30:00-05:00',
'end': '2017-11-18T12:35:30-05:00',
'milestone': 'Rolled over.'
"child": 1,
"start": "2017-11-18T12:30:00-05:00",
"end": "2017-11-18T12:35:30-05:00",
"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)
obj = models.TummyTime.objects.get(pk=response.data['id'])
self.assertEqual(str(obj.duration), '0:05:30')
obj = models.TummyTime.objects.get(pk=response.data["id"])
self.assertEqual(str(obj.duration), "0:05:30")
def test_patch(self):
endpoint = '{}{}/'.format(self.endpoint, 3)
endpoint = "{}{}/".format(self.endpoint, 3)
response = self.client.get(endpoint)
entry = response.data
entry['milestone'] = 'Switched sides!'
response = self.client.patch(endpoint, {
'milestone': entry['milestone'],
})
entry["milestone"] = "Switched sides!"
response = self.client.patch(
endpoint,
{
"milestone": entry["milestone"],
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, entry)
class WeightAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
endpoint = reverse('api:weight-list')
endpoint = reverse("api:weight-list")
model = models.Weight
def test_get(self):
response = self.client.get(self.endpoint)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'][0], {
'id': 2,
'child': 1,
'weight': 9.5,
'date': '2017-11-18',
'notes': 'before feed'
})
self.assertEqual(
response.data["results"][0],
{
"id": 2,
"child": 1,
"weight": 9.5,
"date": "2017-11-18",
"notes": "before feed",
},
)
def test_post(self):
data = {
'child': 1,
'weight': '9.75',
'date': '2017-11-20',
'notes': 'after feed'
"child": 1,
"weight": "9.75",
"date": "2017-11-20",
"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)
obj = models.Weight.objects.get(pk=response.data['id'])
self.assertEqual(str(obj.weight), data['weight'])
self.assertEqual(str(obj.notes), data['notes'])
obj = models.Weight.objects.get(pk=response.data["id"])
self.assertEqual(str(obj.weight), data["weight"])
self.assertEqual(str(obj.notes), data["notes"])
def test_patch(self):
endpoint = '{}{}/'.format(self.endpoint, 2)
endpoint = "{}{}/".format(self.endpoint, 2)
response = self.client.get(endpoint)
entry = response.data
entry['weight'] = 8.25
response = self.client.patch(endpoint, {
'weight': entry['weight'],
})
entry["weight"] = 8.25
response = self.client.patch(
endpoint,
{
"weight": entry["weight"],
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, entry)

View File

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

View File

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

View File

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

View File

@ -9,21 +9,25 @@ from babybuddy import models
class SettingsInline(admin.StackedInline):
model = models.Settings
verbose_name = _('Settings')
verbose_name_plural = _('Settings')
verbose_name = _("Settings")
verbose_name_plural = _("Settings")
can_delete = False
fieldsets = (
(_('Dashboard'), {
'fields': (
'dashboard_refresh_rate',
'dashboard_hide_empty',
'dashboard_hide_age')
}),
(
_("Dashboard"),
{
"fields": (
"dashboard_refresh_rate",
"dashboard_hide_empty",
"dashboard_hide_age",
)
},
),
)
class UserAdmin(BaseUserAdmin):
inlines = (SettingsInline, )
inlines = (SettingsInline,)
admin.site.unregister(User)

View File

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

View File

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

View File

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

View File

@ -4,14 +4,14 @@ from django.core.management.commands import migrate
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):
super(Command, self).handle(*args, **kwargs)
superusers = User.objects.filter(is_superuser=True)
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_staff = True
default_user.save()

View File

@ -12,13 +12,14 @@ from .migrate import Command as Migrate
class Command(BaseCommand):
help = 'Reapplies core migrations and generates fake data.'
help = "Reapplies core migrations and generates fake data."
def __init__(self, *args, **kwargs):
super(Command, self).__init__(*args, **kwargs)
self.UserModel = get_user_model()
self.username_field = self.UserModel._meta.get_field(
self.UserModel.USERNAME_FIELD)
self.UserModel.USERNAME_FIELD
)
# Disable system checks for reset.
self.requires_system_checks = False
@ -28,20 +29,20 @@ class Command(BaseCommand):
Fake().add_arguments(parser)
def handle(self, *args, **options):
verbosity = options['verbosity']
verbosity = options["verbosity"]
# Flush all existing database records.
flush = Flush()
flush.handle(**options)
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.
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()
options['app_label'] = config.name
options['migration_name'] = 'zero'
options["app_label"] = config.name
options["migration_name"] = "zero"
try:
migrate.handle(*args, **options)
@ -51,18 +52,18 @@ class Command(BaseCommand):
# Run other migrations.
migrate = Migrate()
options['app_label'] = None
options['migration_name'] = None
options["app_label"] = None
options["migration_name"] = None
migrate.handle(*args, **options)
# Clear cache.
cache.clear()
if verbosity > 0:
self.stdout.write(self.style.SUCCESS('Cache cleared.'))
self.stdout.write(self.style.SUCCESS("Cache cleared."))
# Populate database with fake data.
fake = Fake()
fake.handle(*args, **options)
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.
"""
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 = [
'%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:%S", # '10/25/2006 14:30:59'
"%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.TIME_FORMAT = 'H:i:s'
formats_en_us.SHORT_DATETIME_FORMAT = "m/d/Y G:i:s"
formats_en_us.TIME_FORMAT = "H:i:s"
else:
# These formats are added to support the locale style of Baby Buddy's
# frontend library, which uses momentjs.
custom_input_formats = [
'%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:%S %p", # '10/25/2006 2:30:59 PM'
"%m/%d/%Y %I:%M %p", # '10/25/2006 2:30 PM'
]
# 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.
formats_en_us.DATETIME_INPUT_FORMATS = \
formats_en_us.DATETIME_INPUT_FORMATS = (
custom_input_formats + formats_en_us.DATETIME_INPUT_FORMATS
)
def update_en_gb_date_formats():
if settings.USE_24_HOUR_TIME_FORMAT:
# 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 = [
'%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:%S", # '25/10/2006 14:30:59'
"%d/%m/%Y %H:%M", # '25/10/2006 14:30'
]
formats_en_gb.SHORT_DATETIME_FORMAT = 'd/m/Y H:i'
formats_en_gb.TIME_FORMAT = 'H:i'
formats_en_gb.SHORT_DATETIME_FORMAT = "d/m/Y H:i"
formats_en_gb.TIME_FORMAT = "H:i"
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
# frontend library, which uses momentjs.
custom_input_formats = [
'%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:%S %p", # '25/10/2006 2:30:59 PM'
"%d/%m/%Y %I:%M %p", # '25/10/2006 2:30 PM'
]
# 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
)
class UserLanguageMiddleware:
"""
Customizes settings based on user language setting.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
user = request.user
if hasattr(user, 'settings') and user.settings.language:
if hasattr(user, "settings") and user.settings.language:
language = user.settings.language
elif request.LANGUAGE_CODE:
language = request.LANGUAGE_CODE
@ -80,9 +83,9 @@ class UserLanguageMiddleware:
language = settings.LANGUAGE_CODE
if language:
if language == 'en-US':
if language == "en-US":
update_en_us_date_formats()
elif language == 'en-GB':
elif language == "en-GB":
update_en_gb_date_formats()
# Set the language before generating the response.
@ -104,12 +107,13 @@ class UserTimezoneMiddleware:
`django.contrib.auth.middleware.AuthenticationMiddleware` because it uses
the request.user object.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
user = request.user
if hasattr(user, 'settings') and user.settings.timezone:
if hasattr(user, "settings") and user.settings.timezone:
try:
timezone.activate(pytz.timezone(user.settings.timezone))
except pytz.UnknownTimeZoneError:
@ -121,20 +125,21 @@ class RollingSessionMiddleware:
"""
Periodically resets the session expiry for existing sessions.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.session.keys():
session_refresh = request.session.get('session_refresh')
session_refresh = request.session.get("session_refresh")
if session_refresh:
try:
delta = int(time.time()) - session_refresh
except (ValueError, TypeError):
delta = settings.ROLLING_SESSION_REFRESH + 1
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)
else:
request.session['session_refresh'] = int(time.time())
request.session["session_refresh"] = int(time.time())
return self.get_response(request)

View File

@ -16,11 +16,44 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name='Settings',
name="Settings",
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')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
(
"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",
),
),
(
"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):
Settings = apps.get_model('babybuddy', 'Settings')
User = apps.get_model('auth', 'User')
Settings = apps.get_model("babybuddy", "Settings")
User = apps.get_model("auth", "User")
for user in User.objects.all():
if Settings.objects.filter(user=user).count() == 0:
settings = Settings.objects.create(user=user)
@ -16,7 +16,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('babybuddy', '0001_initial'),
("babybuddy", "0001_initial"),
]
operations = [

View File

@ -7,13 +7,30 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('babybuddy', '0002_add_settings'),
("babybuddy", "0002_add_settings"),
]
operations = [
migrations.AlterField(
model_name='settings',
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'),
model_name="settings",
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",
),
),
]

View File

@ -6,13 +6,15 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('babybuddy', '0003_add_refresh_help_text'),
("babybuddy", "0003_add_refresh_help_text"),
]
operations = [
migrations.AddField(
model_name='settings',
name='language',
field=models.CharField(choices=[], default='en', max_length=255, verbose_name='Language'),
model_name="settings",
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):
dependencies = [
('babybuddy', '0004_settings_language'),
("babybuddy", "0004_settings_language"),
]
operations = [
migrations.AlterField(
model_name='settings',
name='language',
field=models.CharField(choices=[('en', 'English'), ('fr', 'French')], default='en', max_length=255, verbose_name='Language'),
model_name="settings",
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):
dependencies = [
('babybuddy', '0005_auto_20190502_1701'),
("babybuddy", "0005_auto_20190502_1701"),
]
operations = [
migrations.AlterField(
model_name='settings',
name='language',
field=models.CharField(choices=[('en', 'English'), ('fr', 'French'), ('sv', 'Swedish')], default='en', max_length=255, verbose_name='Language'),
model_name="settings",
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):
dependencies = [
('babybuddy', '0006_auto_20190502_1744'),
("babybuddy", "0006_auto_20190502_1744"),
]
operations = [
migrations.AlterField(
model_name='settings',
name='language',
field=models.CharField(choices=[('en', 'English'), ('fr', 'French'), ('de', 'German'), ('sv', 'Swedish')], default='en', max_length=255, verbose_name='Language'),
model_name="settings",
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):
dependencies = [
('babybuddy', '0007_auto_20190607_1422'),
("babybuddy", "0007_auto_20190607_1422"),
]
operations = [
migrations.AlterField(
model_name='settings',
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'),
model_name="settings",
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):
dependencies = [
('babybuddy', '0013_auto_20210411_1241'),
("babybuddy", "0013_auto_20210411_1241"),
]
operations = [
migrations.AddField(
model_name='settings',
name='dashboard_hide_empty',
field=models.BooleanField(default=False, verbose_name='Hide Empty Dashboard Cards'),
model_name="settings",
name="dashboard_hide_empty",
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):
dependencies = [
('babybuddy', '0016_alter_settings_timezone'),
("babybuddy", "0016_alter_settings_timezone"),
]
operations = [
migrations.AddField(
model_name='settings',
name='dashboard_hide_age',
model_name="settings",
name="dashboard_hide_age",
field=models.DurationField(
choices=[
(None, 'show all data'),
(timezone.timedelta(days=1), '1 day'),
(timezone.timedelta(days=2), '2 days'),
(timezone.timedelta(days=3), '3 days'),
(timezone.timedelta(weeks=1), '1 week'),
(timezone.timedelta(weeks=4), '4 weeks')
(None, "show all data"),
(timezone.timedelta(days=1), "1 day"),
(timezone.timedelta(days=2), "2 days"),
(timezone.timedelta(days=3), "3 days"),
(timezone.timedelta(weeks=1), "1 week"),
(timezone.timedelta(weeks=4), "4 weeks"),
],
default=None,
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):
Settings = apps.get_model('babybuddy', 'Settings')
Settings = apps.get_model("babybuddy", "Settings")
for settings in Settings.objects.all():
if settings.language == 'en':
settings.language = 'en-US'
if settings.language == "en":
settings.language = "en-US"
settings.save()
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():
if settings.language == 'en-US':
settings.language = 'en'
if settings.language == "en-US":
settings.language = "en"
settings.save()
class Migration(migrations.Migration):
dependencies = [
('babybuddy', '0019_alter_settings_timezone'),
("babybuddy", "0019_alter_settings_timezone"),
]
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 -*-
from django.contrib.auth.mixins import AccessMixin, \
LoginRequiredMixin as LoginRequiredMixInBase, \
PermissionRequiredMixin as PermissionRequiredMixinBase
from django.contrib.auth.mixins import (
AccessMixin,
LoginRequiredMixin as LoginRequiredMixInBase,
PermissionRequiredMixin as PermissionRequiredMixinBase,
)
from django.utils.decorators import method_decorator
from django.views.decorators.cache import never_cache
@method_decorator(never_cache, name='dispatch')
@method_decorator(never_cache, name="dispatch")
class LoginRequiredMixin(LoginRequiredMixInBase):
pass
@method_decorator(never_cache, name='dispatch')
@method_decorator(never_cache, name="dispatch")
class PermissionRequiredMixin(PermissionRequiredMixinBase):
login_url = '/login'
login_url = "/login"
@method_decorator(never_cache, name='dispatch')
@method_decorator(never_cache, name="dispatch")
class StaffOnlyMixin(AccessMixin):
"""
Verify the current user is staff.
"""
def dispatch(self, request, *args, **kwargs):
if not request.user.is_staff:
return self.handle_no_permission()

View File

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

View File

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

View File

@ -1,9 +1,9 @@
from .base import *
SECRET_KEY = 'CISECRETKEYIGUESS'
SECRET_KEY = "CISECRETKEYIGUESS"
# Static files (CSS, JavaScript, Images)
# 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
# https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
SECRET_KEY = 'CHANGE ME'
SECRET_KEY = "CHANGE ME"
DEBUG = True
@ -14,13 +14,13 @@ DEBUG = True
# production static files.
# DEBUG = False
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'
STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage"
# Django Rest Framework
# https://www.django-rest-framework.org/
REST_FRAMEWORK['DEFAULT_RENDERER_CLASSES'] = (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"] = (
"rest_framework.renderers.JSONRenderer",
"rest_framework.renderers.BrowsableAPIRenderer",
)

View File

@ -5,32 +5,32 @@ from .base import *
# Default to not allow uploads.
# 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
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {
'default': dj_database_url.config(conn_max_age=500)
}
DATABASES = {"default": dj_database_url.config(conn_max_age=500)}
# Email
# https://docs.djangoproject.com/en/3.0/topics/email/
# https://devcenter.heroku.com/articles/sendgrid#python
SENDGRID_USERNAME = os.environ.get('SENDGRID_USERNAME', None) # noqa: F405
SENDGRID_PASSWORD = os.environ.get('SENDGRID_PASSWORD', None) # noqa: F405
SENDGRID_USERNAME = os.environ.get("SENDGRID_USERNAME", 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
# is accessible via Heroku logs
if SENDGRID_USERNAME and SENDGRID_PASSWORD:
EMAIL_HOST = 'smtp.sendgrid.net'
EMAIL_HOST = "smtp.sendgrid.net"
EMAIL_HOST_USER = SENDGRID_USERNAME
EMAIL_HOST_PASSWORD = SENDGRID_PASSWORD
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_TIMEOUT = 60
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
# See babybuddy.settings.base for additional settings information.
SECRET_KEY = ''
SECRET_KEY = ""
ALLOWED_HOSTS = ['']
ALLOWED_HOSTS = [""]
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, '../data/db.sqlite3'),
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(BASE_DIR, "../data/db.sqlite3"),
}
}
# Media 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
# After setting up SSL, uncomment the settings below for enhanced security of

View File

@ -1,13 +1,13 @@
from .base import *
SECRET_KEY = 'TESTS'
SECRET_KEY = "TESTS"
# Password hasher configuration
# 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
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.MD5PasswordHasher',
"django.contrib.auth.hashers.MD5PasswordHasher",
]
# Axes configuration

View File

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

View File

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

View File

@ -14,10 +14,10 @@ class FormatsTestCase(TestCase):
update_en_us_date_formats()
field = DateTimeField()
supported_custom_examples = [
'01/20/2020 9:30 AM',
'01/20/2020 9:30:03 AM',
'10/01/2020 11:30 PM',
'10/01/2020 11:30:03 AM',
"01/20/2020 9:30 AM",
"01/20/2020 9:30:03 AM",
"10/01/2020 11:30 PM",
"10/01/2020 11:30:03 AM",
]
for example in supported_custom_examples:
@ -28,18 +28,18 @@ class FormatsTestCase(TestCase):
self.fail('Format of "{}" not recognized!'.format(example))
with self.assertRaises(ValidationError):
field.to_python('invalid date string!')
field.to_python("invalid date string!")
@tag('isolate')
@override_settings(LANGUAGE_CODE='en-US', USE_24_HOUR_TIME_FORMAT=True)
@tag("isolate")
@override_settings(LANGUAGE_CODE="en-US", USE_24_HOUR_TIME_FORMAT=True)
def test_use_24_hour_time_format(self):
update_en_us_date_formats()
field = DateTimeField()
supported_custom_examples = [
'10/25/2006 2:30:59',
'10/25/2006 2:30',
'10/25/2006 14:30:59',
'10/25/2006 14:30',
"10/25/2006 2:30:59",
"10/25/2006 2:30",
"10/25/2006 14:30:59",
"10/25/2006 14:30",
]
for example in supported_custom_examples:
@ -50,23 +50,18 @@ class FormatsTestCase(TestCase):
self.fail('Format of "{}" not recognized!'.format(example))
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,
second=59)
self.assertEqual(
date_format(dt, 'DATETIME_FORMAT'), 'Nov. 4, 2011, 23:05:59')
dt = datetime.datetime(year=2011, month=11, day=4, hour=23, minute=5, second=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,
second=59)
self.assertEqual(
date_format(dt, 'SHORT_DATETIME_FORMAT'), '11/04/2011 2:05:59')
dt = datetime.datetime(year=2011, month=11, day=4, hour=2, minute=5, second=59)
self.assertEqual(date_format(dt, "SHORT_DATETIME_FORMAT"), "11/04/2011 2:05:59")
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):
update_en_us_date_formats()
dt = datetime.datetime(year=2021, month=7, day=31, hour=5, minute=5,
second=5)
self.assertEqual(date_format(dt, 'SHORT_MONTH_DAY_FORMAT'), 'Jul 31')
dt = datetime.datetime(year=2021, month=7, day=31, hour=5, minute=5, second=5)
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):
def test_migrate(self):
call_command('migrate', verbosity=0)
self.assertIsInstance(User.objects.get(username='admin'), User)
call_command("migrate", verbosity=0)
self.assertIsInstance(User.objects.get(username="admin"), User)
def test_fake(self):
call_command('migrate', verbosity=0)
call_command('fake', children=1, days=7, verbosity=0)
call_command("migrate", verbosity=0)
call_command("fake", children=1, days=7, verbosity=0)
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)
def test_reset(self):
call_command('reset', verbosity=0, interactive=False)
self.assertIsInstance(User.objects.get(username='admin'), User)
call_command("reset", verbosity=0, interactive=False)
self.assertIsInstance(User.objects.get(username="admin"), User)
self.assertEqual(Child.objects.count(), 1)

View File

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

View File

@ -8,22 +8,18 @@ from babybuddy.models import Settings
class SettingsTestCase(TestCase):
def setUp(self):
call_command('migrate', verbosity=0)
call_command("migrate", verbosity=0)
def test_settings(self):
credentials = {
'username': 'Test',
'password': 'User'
}
credentials = {"username": "Test", "password": "User"}
user = User.objects.create_user(is_superuser=True, **credentials)
self.assertIsInstance(user.settings, Settings)
self.assertEqual(
user.settings.dashboard_refresh_rate_milliseconds, 60000)
self.assertEqual(user.settings.dashboard_refresh_rate_milliseconds, 60000)
user.settings.dashboard_refresh_rate = None
user.save()
self.assertIsNone(user.settings.dashboard_refresh_rate_milliseconds)
user.settings.language = 'fr'
user.settings.language = "fr"
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):
def test_child_count(self):
self.assertEqual(babybuddy_tags.get_child_count(), 0)
Child.objects.create(first_name='Test', last_name='Child',
birth_date=timezone.localdate())
Child.objects.create(
first_name="Test", last_name="Child", birth_date=timezone.localdate()
)
self.assertEqual(babybuddy_tags.get_child_count(), 1)
Child.objects.create(first_name='Test', last_name='Child 2',
birth_date=timezone.localdate())
Child.objects.create(
first_name="Test", last_name="Child 2", birth_date=timezone.localdate()
)
self.assertEqual(babybuddy_tags.get_child_count(), 2)

View File

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

View File

@ -8,54 +8,30 @@ from django.urls import include, path
from . import views
app_patterns = [
path('login/', auth_views.LoginView.as_view(), name='login'),
path('logout/', views.LogoutView.as_view(), name='logout'),
path("login/", auth_views.LoginView.as_view(), name="login"),
path("logout/", views.LogoutView.as_view(), name="logout"),
path(
'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'
"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"),
]
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('api.urls', namespace='api')),
path('', include((app_patterns, 'babybuddy'), namespace='babybuddy')),
path('user/lang', include('django.conf.urls.i18n')),
path('', include('core.urls', namespace='core')),
path('', include('dashboard.urls', namespace='dashboard')),
path('', include('reports.urls', namespace='reports')),
path("admin/", admin.site.urls),
path("", include("api.urls", namespace="api")),
path("", include((app_patterns, "babybuddy"), namespace="babybuddy")),
path("user/lang", include("django.conf.urls.i18n")),
path("", include("core.urls", namespace="core")),
path("", include("dashboard.urls", namespace="dashboard")),
path("", include("reports.urls", namespace="reports")),
]
if settings.DEBUG: # pragma: no cover
urlpatterns += static(
settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT
)
urlpatterns += static(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 babybuddy import forms
from babybuddy.mixins import LoginRequiredMixin, PermissionRequiredMixin, \
StaffOnlyMixin
from babybuddy.mixins import LoginRequiredMixin, PermissionRequiredMixin, StaffOnlyMixin
class RootRouter(LoginRequiredMixin, RedirectView):
"""
Redirects to the site dashboard.
"""
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)
@ -40,86 +40,85 @@ class BabyBuddyFilterView(FilterView):
Disables "strictness" for django-filter. It is unclear from the
documentation exactly what this does...
"""
# TODO Figure out the correct way to use this.
strict = False
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
children = {
o.child for o in context['object_list'] if hasattr(o, "child")
}
children = {o.child for o in context["object_list"] if hasattr(o, "child")}
if len(children) == 1:
context['unique_child'] = True
context["unique_child"] = True
return context
@method_decorator(csrf_protect, name='dispatch')
@method_decorator(never_cache, name='dispatch')
@method_decorator(require_POST, name='dispatch')
@method_decorator(csrf_protect, name="dispatch")
@method_decorator(never_cache, name="dispatch")
@method_decorator(require_POST, name="dispatch")
class LogoutView(LogoutViewBase):
pass
class UserList(StaffOnlyMixin, BabyBuddyFilterView):
model = User
template_name = 'babybuddy/user_list.html'
ordering = 'username'
template_name = "babybuddy/user_list.html"
ordering = "username"
paginate_by = 10
filterset_fields = ('username', 'first_name', 'last_name', 'email')
filterset_fields = ("username", "first_name", "last_name", "email")
class UserAdd(StaffOnlyMixin, PermissionRequiredMixin, SuccessMessageMixin,
CreateView):
class UserAdd(StaffOnlyMixin, PermissionRequiredMixin, SuccessMessageMixin, CreateView):
model = User
template_name = 'babybuddy/user_form.html'
permission_required = ('admin.add_user',)
template_name = "babybuddy/user_form.html"
permission_required = ("admin.add_user",)
form_class = forms.UserAddForm
success_url = reverse_lazy('babybuddy:user-list')
success_message = gettext_lazy('User %(username)s added!')
success_url = reverse_lazy("babybuddy:user-list")
success_message = gettext_lazy("User %(username)s added!")
class UserUpdate(StaffOnlyMixin, PermissionRequiredMixin,
SuccessMessageMixin, UpdateView):
class UserUpdate(
StaffOnlyMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView
):
model = User
template_name = 'babybuddy/user_form.html'
permission_required = ('admin.change_user',)
template_name = "babybuddy/user_form.html"
permission_required = ("admin.change_user",)
form_class = forms.UserUpdateForm
success_url = reverse_lazy('babybuddy:user-list')
success_message = gettext_lazy('User %(username)s updated.')
success_url = reverse_lazy("babybuddy:user-list")
success_message = gettext_lazy("User %(username)s updated.")
class UserDelete(StaffOnlyMixin, PermissionRequiredMixin,
DeleteView, SuccessMessageMixin):
class UserDelete(
StaffOnlyMixin, PermissionRequiredMixin, DeleteView, SuccessMessageMixin
):
model = User
template_name = 'babybuddy/user_confirm_delete.html'
permission_required = ('admin.delete_user',)
success_url = reverse_lazy('babybuddy:user-list')
template_name = "babybuddy/user_confirm_delete.html"
permission_required = ("admin.delete_user",)
success_url = reverse_lazy("babybuddy:user-list")
def get_success_message(self, cleaned_data):
return format_lazy(gettext_lazy(
'User {user} deleted.'), user=self.get_object()
)
return format_lazy(gettext_lazy("User {user} deleted."), user=self.get_object())
class UserPassword(LoginRequiredMixin, View):
"""
Handles user password changes.
"""
form_class = forms.UserPasswordForm
template_name = 'babybuddy/user_password_form.html'
template_name = "babybuddy/user_password_form.html"
def get(self, request):
return render(request, self.template_name, {
'form': self.form_class(request.user)
})
return render(
request, self.template_name, {"form": self.form_class(request.user)}
)
def post(self, request):
form = PasswordChangeForm(request.user, request.POST)
if form.is_valid():
user = form.save()
update_session_auth_hash(request, user)
messages.success(request, _('Password updated.'))
return render(request, self.template_name, {'form': form})
messages.success(request, _("Password updated."))
return render(request, self.template_name, {"form": form})
class UserSettings(LoginRequiredMixin, View):
@ -127,42 +126,47 @@ class UserSettings(LoginRequiredMixin, View):
Handles both the User and Settings models.
Based on this SO answer: https://stackoverflow.com/a/45056835.
"""
form_user_class = forms.UserForm
form_settings_class = forms.UserSettingsForm
template_name = 'babybuddy/user_settings_form.html'
template_name = "babybuddy/user_settings_form.html"
def get(self, request):
return render(request, self.template_name, {
'form_user': self.form_user_class(instance=request.user),
'form_settings': self.form_settings_class(
instance=request.user.settings)
})
return render(
request,
self.template_name,
{
"form_user": self.form_user_class(instance=request.user),
"form_settings": self.form_settings_class(
instance=request.user.settings
),
},
)
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)
messages.success(request, _('User API key regenerated.'))
return redirect('babybuddy:user-settings')
messages.success(request, _("User API key regenerated."))
return redirect("babybuddy:user-settings")
form_user = self.form_user_class(
instance=request.user,
data=request.POST)
form_user = self.form_user_class(instance=request.user, data=request.POST)
form_settings = self.form_settings_class(
instance=request.user.settings,
data=request.POST)
instance=request.user.settings, data=request.POST
)
if form_user.is_valid() and form_settings.is_valid():
user = form_user.save(commit=False)
user_settings = form_settings.save(commit=False)
user.settings = user_settings
user.save()
translation.activate(user.settings.language)
messages.success(request, _('Settings saved!'))
messages.success(request, _("Settings saved!"))
translation.deactivate()
return set_language(request)
return render(request, self.template_name, {
'user_form': form_user,
'settings_form': form_settings
})
return render(
request,
self.template_name,
{"user_form": form_user, "settings_form": form_settings},
)
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
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):
id = fields.Field(attribute='id')
child = fields.Field(attribute='child_id', column_name='child_id')
child_first_name = fields.Field(
attribute='child__first_name', readonly=True)
child_last_name = fields.Field(attribute='child__last_name', readonly=True)
id = fields.Field(attribute="id")
child = fields.Field(attribute="child_id", column_name="child_id")
child_first_name = fields.Field(attribute="child__first_name", readonly=True)
child_last_name = fields.Field(attribute="child__last_name", readonly=True)
class Meta:
clean_model_instances = True
exclude = ('duration',)
exclude = ("duration",)
class ChildImportExportResource(resources.ModelResource):
class Meta:
model = models.Child
exclude = ('picture', 'slug')
exclude = ("picture", "slug")
@admin.register(models.Child)
class ChildAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin):
list_display = ('first_name', 'last_name', 'birth_date', 'slug')
list_filter = ('last_name',)
search_fields = ('first_name', 'last_name', 'birth_date')
fields = ['first_name', 'last_name', 'birth_date']
if settings.BABY_BUDDY['ALLOW_UPLOADS']:
fields.append('picture')
list_display = ("first_name", "last_name", "birth_date", "slug")
list_filter = ("last_name",)
search_fields = ("first_name", "last_name", "birth_date")
fields = ["first_name", "last_name", "birth_date"]
if settings.BABY_BUDDY["ALLOW_UPLOADS"]:
fields.append("picture")
resource_class = ChildImportExportResource
@ -43,11 +42,13 @@ class DiaperChangeImportExportResource(ImportExportResourceBase):
@admin.register(models.DiaperChange)
class DiaperChangeAdmin(ImportExportMixin, ExportActionMixin,
admin.ModelAdmin):
list_display = ('child', 'time', 'wet', 'solid', 'color')
list_filter = ('child', 'wet', 'solid', 'color')
search_fields = ('child__first_name', 'child__last_name',)
class DiaperChangeAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin):
list_display = ("child", "time", "wet", "solid", "color")
list_filter = ("child", "wet", "solid", "color")
search_fields = (
"child__first_name",
"child__last_name",
)
resource_class = DiaperChangeImportExportResource
@ -58,11 +59,18 @@ class FeedingImportExportResource(ImportExportResourceBase):
@admin.register(models.Feeding)
class FeedingAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin):
list_display = ('start', 'end', 'duration', 'child', 'type', 'method',
'amount')
list_filter = ('child', 'type', 'method',)
search_fields = ('child__first_name', 'child__last_name', 'type',
'method',)
list_display = ("start", "end", "duration", "child", "type", "method", "amount")
list_filter = (
"child",
"type",
"method",
)
search_fields = (
"child__first_name",
"child__last_name",
"type",
"method",
)
resource_class = FeedingImportExportResource
@ -73,9 +81,13 @@ class NoteImportExportResource(ImportExportResourceBase):
@admin.register(models.Note)
class NoteAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin):
list_display = ('time', 'child', 'note',)
list_filter = ('child',)
search_fields = ('child__last_name',)
list_display = (
"time",
"child",
"note",
)
list_filter = ("child",)
search_fields = ("child__last_name",)
resource_class = NoteImportExportResource
@ -86,9 +98,12 @@ class SleepImportExportResource(ImportExportResourceBase):
@admin.register(models.Sleep)
class SleepAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin):
list_display = ('start', 'end', 'duration', 'child', 'nap')
list_filter = ('child',)
search_fields = ('child__first_name', 'child__last_name',)
list_display = ("start", "end", "duration", "child", "nap")
list_filter = ("child",)
search_fields = (
"child__first_name",
"child__last_name",
)
resource_class = SleepImportExportResource
@ -99,18 +114,25 @@ class TemperatureImportExportResource(ImportExportResourceBase):
@admin.register(models.Temperature)
class TemperatureAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin):
list_display = ('child', 'temperature', 'time',)
list_filter = ('child',)
search_fields = ('child__first_name', 'child__last_name', 'temperature',)
list_display = (
"child",
"temperature",
"time",
)
list_filter = ("child",)
search_fields = (
"child__first_name",
"child__last_name",
"temperature",
)
resource_class = TemperatureImportExportResource
@admin.register(models.Timer)
class TimerAdmin(admin.ModelAdmin):
list_display = ('name', 'child', 'start', 'end', 'duration', 'active',
'user')
list_filter = ('child', 'active', 'user')
search_fields = ('child__first_name', 'child__last_name', 'name', 'user')
list_display = ("name", "child", "start", "end", "duration", "active", "user")
list_filter = ("child", "active", "user")
search_fields = ("child__first_name", "child__last_name", "name", "user")
class TummyTimeImportExportResource(ImportExportResourceBase):
@ -120,9 +142,19 @@ class TummyTimeImportExportResource(ImportExportResourceBase):
@admin.register(models.TummyTime)
class TummyTimeAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin):
list_display = ('start', 'end', 'duration', 'child', 'milestone',)
list_filter = ('child',)
search_fields = ('child__first_name', 'child__last_name', 'milestone',)
list_display = (
"start",
"end",
"duration",
"child",
"milestone",
)
list_filter = ("child",)
search_fields = (
"child__first_name",
"child__last_name",
"milestone",
)
resource_class = TummyTimeImportExportResource
@ -133,7 +165,15 @@ class WeightImportExportResource(ImportExportResourceBase):
@admin.register(models.Weight)
class WeightAdmin(ImportExportMixin, ExportActionMixin, admin.ModelAdmin):
list_display = ('child', 'weight', 'date',)
list_filter = ('child',)
search_fields = ('child__first_name', 'child__last_name', 'weight',)
list_display = (
"child",
"weight",
"date",
)
list_filter = ("child",)
search_fields = (
"child__first_name",
"child__last_name",
"weight",
)
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).
if kwargs.get('instance', None):
if kwargs.get("instance", None):
return kwargs
# Add the "initial" kwarg if it does not already exist.
if not kwargs.get('initial'):
if not kwargs.get("initial"):
kwargs.update(initial={})
# 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:
kwargs['initial'].update({
'child': models.Child.objects.filter(slug=child_slug).first(),
})
kwargs["initial"].update(
{
"child": models.Child.objects.filter(slug=child_slug).first(),
}
)
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.
timer_id = kwargs.get('timer', None)
timer_id = kwargs.get("timer", None)
if timer_id:
timer = models.Timer.objects.get(id=timer_id)
kwargs['initial'].update({
'timer': timer,
'start': timer.start,
'end': timer.end or timezone.now()
})
kwargs["initial"].update(
{"timer": timer, "start": timer.start, "end": timer.end or timezone.now()}
)
# Set type and method values for Feeding instance based on last feed.
if form_type == FeedingForm and 'child' in kwargs['initial']:
last_feeding = models.Feeding.objects.filter(
child=kwargs['initial']['child']).order_by('end').last()
if form_type == FeedingForm and "child" in kwargs["initial"]:
last_feeding = (
models.Feeding.objects.filter(child=kwargs["initial"]["child"])
.order_by("end")
.last()
)
if last_feeding:
last_method = last_feeding.method
last_feed_args = {'type': last_feeding.type}
if last_method not in ['left breast', 'right breast']:
last_feed_args['method'] = last_method
kwargs['initial'].update(last_feed_args)
last_feed_args = {"type": last_feeding.type}
if last_method not in ["left breast", "right breast"]:
last_feed_args["method"] = last_method
kwargs["initial"].update(last_feed_args)
# Remove custom kwargs so they do not interfere with `super` calls.
for key in ['child', 'timer']:
for key in ["child", "timer"]:
try:
kwargs.pop(key)
except KeyError:
@ -67,7 +70,7 @@ def set_initial_values(kwargs, form_type):
class CoreModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
# 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))
super(CoreModelForm, self).__init__(*args, **kwargs)
@ -85,18 +88,16 @@ class CoreModelForm(forms.ModelForm):
class ChildForm(forms.ModelForm):
class Meta:
model = models.Child
fields = [
'first_name',
'last_name',
'birth_date'
]
if settings.BABY_BUDDY['ALLOW_UPLOADS']:
fields.append('picture')
fields = ["first_name", "last_name", "birth_date"]
if settings.BABY_BUDDY["ALLOW_UPLOADS"]:
fields.append("picture")
widgets = {
'birth_date': forms.DateInput(attrs={
'autocomplete': 'off',
'data-target': '#datetimepicker_date',
}),
"birth_date": forms.DateInput(
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_date",
}
),
}
@ -108,10 +109,11 @@ class ChildDeleteForm(forms.ModelForm):
fields = []
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):
raise forms.ValidationError(
_('Name does not match child name.'), code='confirm_mismatch')
_("Name does not match child name."), code="confirm_mismatch"
)
return confirm_name
def save(self, commit=True):
@ -123,88 +125,104 @@ class ChildDeleteForm(forms.ModelForm):
class DiaperChangeForm(CoreModelForm):
class Meta:
model = models.DiaperChange
fields = ['child', 'time', 'wet', 'solid', 'color', 'amount', 'notes']
fields = ["child", "time", "wet", "solid", "color", "amount", "notes"]
widgets = {
'time': forms.DateTimeInput(attrs={
'autocomplete': 'off',
'data-target': '#datetimepicker_time',
}),
'notes': forms.Textarea(attrs={'rows': 5}),
"time": forms.DateTimeInput(
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_time",
}
),
"notes": forms.Textarea(attrs={"rows": 5}),
}
class FeedingForm(CoreModelForm):
class Meta:
model = models.Feeding
fields = ['child', 'start', 'end', 'type', 'method', 'amount', 'notes']
fields = ["child", "start", "end", "type", "method", "amount", "notes"]
widgets = {
'start': forms.DateTimeInput(attrs={
'autocomplete': 'off',
'data-target': '#datetimepicker_start',
}),
'end': forms.DateTimeInput(attrs={
'autocomplete': 'off',
'data-target': '#datetimepicker_end',
}),
'notes': forms.Textarea(attrs={'rows': 5}),
"start": forms.DateTimeInput(
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_start",
}
),
"end": forms.DateTimeInput(
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_end",
}
),
"notes": forms.Textarea(attrs={"rows": 5}),
}
class NoteForm(CoreModelForm):
class Meta:
model = models.Note
fields = ['child', 'note', 'time']
fields = ["child", "note", "time"]
widgets = {
'time': forms.DateTimeInput(attrs={
'autocomplete': 'off',
'data-target': '#datetimepicker_time',
}),
"time": forms.DateTimeInput(
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_time",
}
),
}
class SleepForm(CoreModelForm):
class Meta:
model = models.Sleep
fields = ['child', 'start', 'end', 'notes']
fields = ["child", "start", "end", "notes"]
widgets = {
'start': forms.DateTimeInput(attrs={
'autocomplete': 'off',
'data-target': '#datetimepicker_start',
}),
'end': forms.DateTimeInput(attrs={
'autocomplete': 'off',
'data-target': '#datetimepicker_end',
}),
'notes': forms.Textarea(attrs={'rows': 5}),
"start": forms.DateTimeInput(
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_start",
}
),
"end": forms.DateTimeInput(
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_end",
}
),
"notes": forms.Textarea(attrs={"rows": 5}),
}
class TemperatureForm(CoreModelForm):
class Meta:
model = models.Temperature
fields = ['child', 'temperature', 'time', 'notes']
fields = ["child", "temperature", "time", "notes"]
widgets = {
'time': forms.DateTimeInput(attrs={
'autocomplete': 'off',
'data-target': '#datetimepicker_time',
}),
'notes': forms.Textarea(attrs={'rows': 5}),
"time": forms.DateTimeInput(
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_time",
}
),
"notes": forms.Textarea(attrs={"rows": 5}),
}
class TimerForm(CoreModelForm):
class Meta:
model = models.Timer
fields = ['child', 'name', 'start']
fields = ["child", "name", "start"]
widgets = {
'start': forms.DateTimeInput(attrs={
'autocomplete': 'off',
'data-target': '#datetimepicker_start',
})
"start": forms.DateTimeInput(
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_start",
}
)
}
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
self.user = kwargs.pop("user")
super(TimerForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
@ -217,66 +235,78 @@ class TimerForm(CoreModelForm):
class TummyTimeForm(CoreModelForm):
class Meta:
model = models.TummyTime
fields = ['child', 'start', 'end', 'milestone']
fields = ["child", "start", "end", "milestone"]
widgets = {
'start': forms.DateTimeInput(attrs={
'autocomplete': 'off',
'data-target': '#datetimepicker_start',
}),
'end': forms.DateTimeInput(attrs={
'autocomplete': 'off',
'data-target': '#datetimepicker_end',
}),
"start": forms.DateTimeInput(
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_start",
}
),
"end": forms.DateTimeInput(
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_end",
}
),
}
class WeightForm(CoreModelForm):
class Meta:
model = models.Weight
fields = ['child', 'weight', 'date', 'notes']
fields = ["child", "weight", "date", "notes"]
widgets = {
'date': forms.DateInput(attrs={
'autocomplete': 'off',
'data-target': '#datetimepicker_date',
}),
'notes': forms.Textarea(attrs={'rows': 5}),
"date": forms.DateInput(
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_date",
}
),
"notes": forms.Textarea(attrs={"rows": 5}),
}
class HeightForm(CoreModelForm):
class Meta:
model = models.Height
fields = ['child', 'height', 'date', 'notes']
fields = ["child", "height", "date", "notes"]
widgets = {
'date': forms.DateInput(attrs={
'autocomplete': 'off',
'data-target': '#datetimepicker_date',
}),
'notes': forms.Textarea(attrs={'rows': 5}),
"date": forms.DateInput(
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_date",
}
),
"notes": forms.Textarea(attrs={"rows": 5}),
}
class HeadCircumferenceForm(CoreModelForm):
class Meta:
model = models.HeadCircumference
fields = ['child', 'head_circumference', 'date', 'notes']
fields = ["child", "head_circumference", "date", "notes"]
widgets = {
'date': forms.DateInput(attrs={
'autocomplete': 'off',
'data-target': '#datetimepicker_date',
}),
'notes': forms.Textarea(attrs={'rows': 5}),
"date": forms.DateInput(
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_date",
}
),
"notes": forms.Textarea(attrs={"rows": 5}),
}
class BMIForm(CoreModelForm):
class Meta:
model = models.BMI
fields = ['child', 'bmi', 'date', 'notes']
fields = ["child", "bmi", "date", "notes"]
widgets = {
'date': forms.DateInput(attrs={
'autocomplete': 'off',
'data-target': '#datetimepicker_date',
}),
'notes': forms.Textarea(attrs={'rows': 5}),
"date": forms.DateInput(
attrs={
"autocomplete": "off",
"data-target": "#datetimepicker_date",
}
),
"notes": forms.Textarea(attrs={"rows": 5}),
}

View File

@ -15,109 +15,238 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name='Child',
name="Child",
fields=[
('id', models.AutoField(auto_created=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)),
(
"id",
models.AutoField(
auto_created=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={
'ordering': ['last_name', 'first_name'],
'default_permissions': ('view', 'add', 'change', 'delete'),
'verbose_name_plural': 'Children',
"ordering": ["last_name", "first_name"],
"default_permissions": ("view", "add", "change", "delete"),
"verbose_name_plural": "Children",
},
),
migrations.CreateModel(
name='DiaperChange',
name="DiaperChange",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, 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')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
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={
'ordering': ['-time'],
'default_permissions': ('view', 'add', 'change', 'delete'),
"ordering": ["-time"],
"default_permissions": ("view", "add", "change", "delete"),
},
),
migrations.CreateModel(
name='Feeding',
name="Feeding",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('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')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("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={
'ordering': ['-start'],
'default_permissions': ('view', 'add', 'change', 'delete'),
"ordering": ["-start"],
"default_permissions": ("view", "add", "change", "delete"),
},
),
migrations.CreateModel(
name='Note',
name="Note",
fields=[
('id', models.AutoField(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')),
(
"id",
models.AutoField(
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={
'ordering': ['-time'],
'default_permissions': ('view', 'add', 'change', 'delete'),
"ordering": ["-time"],
"default_permissions": ("view", "add", "change", "delete"),
},
),
migrations.CreateModel(
name='Sleep',
name="Sleep",
fields=[
('id', models.AutoField(auto_created=True, 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')),
(
"id",
models.AutoField(
auto_created=True,
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={
'ordering': ['-start'],
'default_permissions': ('view', 'add', 'change', 'delete'),
'verbose_name_plural': 'Sleep',
"ordering": ["-start"],
"default_permissions": ("view", "add", "change", "delete"),
"verbose_name_plural": "Sleep",
},
),
migrations.CreateModel(
name='Timer',
name="Timer",
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)),
('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)),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
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={
'ordering': ['-active', '-start', '-end'],
'default_permissions': ('view', 'add', 'change', 'delete'),
"ordering": ["-active", "-start", "-end"],
"default_permissions": ("view", "add", "change", "delete"),
},
),
migrations.CreateModel(
name='TummyTime',
name="TummyTime",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, 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')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
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={
'ordering': ['-start'],
'default_permissions': ('view', 'add', 'change', 'delete'),
"ordering": ["-start"],
"default_permissions": ("view", "add", "change", "delete"),
},
),
]

View File

@ -7,13 +7,15 @@ import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
("core", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name='timer',
name='start',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Start Time'),
model_name="timer",
name="start",
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):
dependencies = [
('core', '0002_auto_20171028_1257'),
("core", "0002_auto_20171028_1257"),
]
operations = [
migrations.CreateModel(
name='Weight',
name="Weight",
fields=[
('id', models.AutoField(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')),
(
"id",
models.AutoField(
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={
'default_permissions': ('view', 'add', 'change', 'delete'),
'verbose_name_plural': 'Weight',
'ordering': ['-date'],
"default_permissions": ("view", "add", "change", "delete"),
"verbose_name_plural": "Weight",
"ordering": ["-date"],
},
),
]

View File

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

View File

@ -6,13 +6,22 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0005_auto_20190416_2048'),
("core", "0005_auto_20190416_2048"),
]
operations = [
migrations.AlterField(
model_name='feeding',
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'),
model_name="feeding",
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):
dependencies = [
('core', '0006_auto_20190502_1701'),
("core", "0006_auto_20190502_1701"),
]
operations = [
migrations.CreateModel(
name='Temperature',
name="Temperature",
fields=[
('id', models.AutoField(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')),
(
"id",
models.AutoField(
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={
'verbose_name': 'Temperature',
'verbose_name_plural': 'Temperature',
'ordering': ['-time'],
'default_permissions': ('view', 'add', 'change', 'delete'),
"verbose_name": "Temperature",
"verbose_name_plural": "Temperature",
"ordering": ["-time"],
"default_permissions": ("view", "add", "change", "delete"),
},
),
]

View File

@ -6,13 +6,21 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0007_temperature'),
("core", "0007_temperature"),
]
operations = [
migrations.AlterField(
model_name='feeding',
name='type',
field=models.CharField(choices=[('breast milk', 'Breast milk'), ('formula', 'Formula'), ('fortified breast milk', 'Fortified breast milk')], max_length=255, verbose_name='Type'),
model_name="feeding",
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):
dependencies = [
('core', '0008_auto_20190607_1422'),
("core", "0008_auto_20190607_1422"),
]
operations = [
migrations.AddField(
model_name='diaperchange',
name='amount',
field=models.FloatField(blank=True, null=True, verbose_name='Amount'),
model_name="diaperchange",
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):
dependencies = [
('core', '0009_diaperchange_amount'),
("core", "0009_diaperchange_amount"),
]
operations = [
migrations.AddField(
model_name='timer',
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'),
model_name="timer",
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):
dependencies = [
('core', '0010_timer_child'),
("core", "0010_timer_child"),
]
operations = [
migrations.AddField(
model_name='diaperchange',
name='notes',
field=models.TextField(blank=True, null=True, verbose_name='Notes'),
model_name="diaperchange",
name="notes",
field=models.TextField(blank=True, null=True, verbose_name="Notes"),
),
migrations.AddField(
model_name='feeding',
name='notes',
field=models.TextField(blank=True, null=True, verbose_name='Notes'),
model_name="feeding",
name="notes",
field=models.TextField(blank=True, null=True, verbose_name="Notes"),
),
migrations.AddField(
model_name='sleep',
name='notes',
field=models.TextField(blank=True, null=True, verbose_name='Notes'),
model_name="sleep",
name="notes",
field=models.TextField(blank=True, null=True, verbose_name="Notes"),
),
migrations.AddField(
model_name='temperature',
name='notes',
field=models.TextField(blank=True, null=True, verbose_name='Notes'),
model_name="temperature",
name="notes",
field=models.TextField(blank=True, null=True, verbose_name="Notes"),
),
migrations.AddField(
model_name='weight',
name='notes',
field=models.TextField(blank=True, null=True, verbose_name='Notes'),
model_name="weight",
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):
dependencies = [
('core', '0011_auto_20200214_1939'),
("core", "0011_auto_20200214_1939"),
]
operations = [
migrations.AlterField(
model_name='note',
name='time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Time'),
model_name="note",
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):
dependencies = [
('core', '0012_auto_20200813_0238'),
("core", "0012_auto_20200813_0238"),
]
operations = [
migrations.AlterField(
model_name='feeding',
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'),
model_name="feeding",
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(
model_name='feeding',
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'),
model_name="feeding",
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):
dependencies = [
('core', '0013_auto_20210415_0528'),
("core", "0013_auto_20210415_0528"),
]
operations = [
migrations.AlterField(
model_name='child',
name='slug',
field=models.SlugField(allow_unicode=True, editable=False, max_length=100, unique=True, verbose_name='Slug'),
model_name="child",
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):
# The model must be imported to ensure its overridden `save` method is run.
from core import models
for sleep in models.Sleep.objects.all():
sleep.save()
class Migration(migrations.Migration):
dependencies = [
('core', '0014_alter_child_slug'),
("core", "0014_alter_child_slug"),
]
operations = [
migrations.AddField(
model_name='sleep',
name='napping',
field=models.BooleanField(null=True, verbose_name='Napping'),
model_name="sleep",
name="napping",
field=models.BooleanField(null=True, verbose_name="Napping"),
),
migrations.RunPython(set_napping, reverse_code=migrations.RunPython.noop),
migrations.AlterField(
model_name='sleep',
name='napping',
field=models.BooleanField(verbose_name='Napping'),
model_name="sleep",
name="napping",
field=models.BooleanField(verbose_name="Napping"),
),
]

View File

@ -6,13 +6,15 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0015_add_nap_field_for_sleep'),
("core", "0015_add_nap_field_for_sleep"),
]
operations = [
migrations.AlterField(
model_name='sleep',
name='napping',
field=models.BooleanField(editable=False, null=True, verbose_name='Napping'),
model_name="sleep",
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):
dependencies = [
('core', '0016_alter_sleep_napping'),
("core", "0016_alter_sleep_napping"),
]
operations = [
migrations.AlterField(
model_name='child',
name='last_name',
field=models.CharField(blank=True, max_length=255, verbose_name='Last name'),
model_name="child",
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):
dependencies = [
('core', '0017_alter_child_last_name'),
("core", "0017_alter_child_last_name"),
]
operations = [
migrations.CreateModel(
name='Height',
name="Height",
fields=[
('id', models.AutoField(auto_created=True, 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')),
(
"id",
models.AutoField(
auto_created=True,
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={
'verbose_name': 'Height',
'verbose_name_plural': 'Height',
'ordering': ['-date'],
'default_permissions': ('view', 'add', 'change', 'delete'),
"verbose_name": "Height",
"verbose_name_plural": "Height",
"ordering": ["-date"],
"default_permissions": ("view", "add", "change", "delete"),
},
),
migrations.CreateModel(
name='HeadCircumference',
name="HeadCircumference",
fields=[
('id', models.AutoField(auto_created=True, 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')),
(
"id",
models.AutoField(
auto_created=True,
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={
'verbose_name': 'Head Circumference',
'verbose_name_plural': 'Head Circumference',
'ordering': ['-date'],
'default_permissions': ('view', 'add', 'change', 'delete'),
"verbose_name": "Head Circumference",
"verbose_name_plural": "Head Circumference",
"ordering": ["-date"],
"default_permissions": ("view", "add", "change", "delete"),
},
),
migrations.CreateModel(
name='BMI',
name="BMI",
fields=[
('id', models.AutoField(auto_created=True, 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')),
(
"id",
models.AutoField(
auto_created=True,
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={
'verbose_name': 'BMI',
'verbose_name_plural': 'BMI',
'ordering': ['-date'],
'default_permissions': ('view', 'add', 'change', 'delete'),
"verbose_name": "BMI",
"verbose_name_plural": "BMI",
"ordering": ["-date"],
"default_permissions": ("view", "add", "change", "delete"),
},
),
]

View File

@ -20,8 +20,8 @@ def validate_date(date, field_name):
"""
if date and date > timezone.localdate():
raise ValidationError(
{field_name: _('Date can not be in the future.')},
code='date_invalid')
{field_name: _("Date can not be in the future.")}, code="date_invalid"
)
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 > model.end:
raise ValidationError(
_('Start time must come before end time.'),
code='end_before_start')
_("Start time must come before end time."), code="end_before_start"
)
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):
@ -53,8 +53,9 @@ def validate_unique_period(queryset, model):
if model.start and model.end:
if queryset.filter(start__lt=model.end, end__gt=model.start):
raise ValidationError(
_('Another entry intersects the specified time period.'),
code='period_intersection')
_("Another entry intersects the specified time period."),
code="period_intersection",
)
def validate_time(time, field_name):
@ -66,47 +67,38 @@ def validate_time(time, field_name):
"""
if time and time > timezone.localtime():
raise ValidationError(
{field_name: _('Date/time can not be in the future.')},
code='time_invalid')
{field_name: _("Date/time can not be in the future.")}, code="time_invalid"
)
class Child(models.Model):
model_name = 'child'
first_name = models.CharField(max_length=255, verbose_name=_('First name'))
model_name = "child"
first_name = models.CharField(max_length=255, verbose_name=_("First name"))
last_name = models.CharField(
blank=True,
max_length=255,
verbose_name=_('Last name')
)
birth_date = models.DateField(
blank=False,
null=False,
verbose_name=_('Birth date')
blank=True, max_length=255, verbose_name=_("Last name")
)
birth_date = models.DateField(blank=False, null=False, verbose_name=_("Birth date"))
slug = models.SlugField(
allow_unicode=True,
blank=False,
editable=False,
max_length=100,
unique=True,
verbose_name=_('Slug')
verbose_name=_("Slug"),
)
picture = models.ImageField(
blank=True,
null=True,
upload_to='child/picture/',
verbose_name=_('Picture')
blank=True, null=True, upload_to="child/picture/", verbose_name=_("Picture")
)
objects = models.Manager()
cache_key_count = 'core.child.count'
cache_key_count = "core.child.count"
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['last_name', 'first_name']
verbose_name = _('Child')
verbose_name_plural = _('Children')
default_permissions = ("view", "add", "change", "delete")
ordering = ["last_name", "first_name"]
verbose_name = _("Child")
verbose_name_plural = _("Children")
def __str__(self):
return self.name()
@ -124,132 +116,119 @@ class Child(models.Model):
if not self.last_name:
return self.first_name
if reverse:
return '{}, {}'.format(self.last_name, self.first_name)
return '{} {}'.format(self.first_name, self.last_name)
return "{}, {}".format(self.last_name, self.first_name)
return "{} {}".format(self.first_name, self.last_name)
@classmethod
def count(cls):
""" Get a (cached) count of total number of Child instances. """
"""Get a (cached) count of total number of Child instances."""
return cache.get_or_set(cls.cache_key_count, Child.objects.count, None)
class DiaperChange(models.Model):
model_name = 'diaperchange'
model_name = "diaperchange"
child = models.ForeignKey(
'Child',
"Child",
on_delete=models.CASCADE,
related_name='diaper_change',
verbose_name=_('Child')
related_name="diaper_change",
verbose_name=_("Child"),
)
time = models.DateTimeField(
blank=False,
null=False,
verbose_name=_('Time')
)
wet = models.BooleanField(verbose_name=_('Wet'))
solid = models.BooleanField(verbose_name=_('Solid'))
time = models.DateTimeField(blank=False, null=False, verbose_name=_("Time"))
wet = models.BooleanField(verbose_name=_("Wet"))
solid = models.BooleanField(verbose_name=_("Solid"))
color = models.CharField(
blank=True,
choices=[
('black', _('Black')),
('brown', _('Brown')),
('green', _('Green')),
('yellow', _('Yellow')),
("black", _("Black")),
("brown", _("Brown")),
("green", _("Green")),
("yellow", _("Yellow")),
],
max_length=255,
verbose_name=_('Color')
verbose_name=_("Color"),
)
amount = models.FloatField(blank=True, null=True, verbose_name=_('Amount'))
notes = models.TextField(blank=True, null=True, verbose_name=_('Notes'))
amount = models.FloatField(blank=True, null=True, verbose_name=_("Amount"))
notes = models.TextField(blank=True, null=True, verbose_name=_("Notes"))
objects = models.Manager()
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-time']
verbose_name = _('Diaper Change')
verbose_name_plural = _('Diaper Changes')
default_permissions = ("view", "add", "change", "delete")
ordering = ["-time"]
verbose_name = _("Diaper Change")
verbose_name_plural = _("Diaper Changes")
def __str__(self):
return str(_('Diaper Change'))
return str(_("Diaper Change"))
def attributes(self):
attributes = []
if self.wet:
attributes.append(self._meta.get_field('wet').verbose_name)
attributes.append(self._meta.get_field("wet").verbose_name)
if self.solid:
attributes.append(self._meta.get_field('solid').verbose_name)
attributes.append(self._meta.get_field("solid").verbose_name)
if self.color:
attributes.append(self.get_color_display())
return attributes
def clean(self):
validate_time(self.time, 'time')
validate_time(self.time, "time")
# One or both of Wet and Solid is required.
if not self.wet and not self.solid:
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):
model_name = 'feeding'
model_name = "feeding"
child = models.ForeignKey(
'Child',
"Child",
on_delete=models.CASCADE,
related_name='feeding',
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')
related_name="feeding",
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"))
duration = models.DurationField(
editable=False,
null=True,
verbose_name=_('Duration')
editable=False, null=True, verbose_name=_("Duration")
)
type = models.CharField(
choices=[
('breast milk', _('Breast milk')),
('formula', _('Formula')),
('fortified breast milk', _('Fortified breast milk')),
('solid food', _('Solid food')),
("breast milk", _("Breast milk")),
("formula", _("Formula")),
("fortified breast milk", _("Fortified breast milk")),
("solid food", _("Solid food")),
],
max_length=255,
verbose_name=_('Type')
verbose_name=_("Type"),
)
method = 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')),
("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')
verbose_name=_("Method"),
)
amount = models.FloatField(blank=True, null=True, verbose_name=_('Amount'))
notes = models.TextField(blank=True, null=True, verbose_name=_('Notes'))
amount = models.FloatField(blank=True, null=True, verbose_name=_("Amount"))
notes = models.TextField(blank=True, null=True, verbose_name=_("Notes"))
objects = models.Manager()
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-start']
verbose_name = _('Feeding')
verbose_name_plural = _('Feedings')
default_permissions = ("view", "add", "change", "delete")
ordering = ["-start"]
verbose_name = _("Feeding")
verbose_name_plural = _("Feedings")
def __str__(self):
return str(_('Feeding'))
return str(_("Feeding"))
def save(self, *args, **kwargs):
if self.start and self.end:
@ -257,37 +236,32 @@ class Feeding(models.Model):
super(Feeding, self).save(*args, **kwargs)
def clean(self):
validate_time(self.start, 'start')
validate_time(self.end, 'end')
validate_time(self.start, "start")
validate_time(self.end, "end")
validate_duration(self)
validate_unique_period(Feeding.objects.filter(child=self.child), self)
class Note(models.Model):
model_name = 'note'
model_name = "note"
child = models.ForeignKey(
'Child',
on_delete=models.CASCADE,
related_name='note',
verbose_name=_('Child')
"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(
default=timezone.now,
blank=False,
verbose_name=_('Time')
default=timezone.now, blank=False, verbose_name=_("Time")
)
objects = models.Manager()
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-time']
verbose_name = _('Note')
verbose_name_plural = _('Notes')
default_permissions = ("view", "add", "change", "delete")
ordering = ["-time"]
verbose_name = _("Note")
verbose_name_plural = _("Notes")
def __str__(self):
return str(_('Note'))
return str(_("Note"))
class NapsManager(models.Manager):
@ -297,53 +271,38 @@ class NapsManager(models.Manager):
class Sleep(models.Model):
model_name = 'sleep'
model_name = "sleep"
child = models.ForeignKey(
'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')
"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"))
duration = models.DurationField(
editable=False,
null=True,
verbose_name=_('Duration')
editable=False, 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()
naps = NapsManager()
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-start']
verbose_name = _('Sleep')
verbose_name_plural = _('Sleep')
default_permissions = ("view", "add", "change", "delete")
ordering = ["-start"]
verbose_name = _("Sleep")
verbose_name_plural = _("Sleep")
def __str__(self):
return str(_('Sleep'))
return str(_("Sleep"))
@property
def nap(self):
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(
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()
return nap_start_min <= local_start_time <= nap_start_max
@ -354,115 +313,94 @@ class Sleep(models.Model):
super(Sleep, self).save(*args, **kwargs)
def clean(self):
validate_time(self.start, 'start')
validate_time(self.end, 'end')
validate_time(self.start, "start")
validate_time(self.end, "end")
validate_duration(self)
validate_unique_period(Sleep.objects.filter(child=self.child), self)
class Temperature(models.Model):
model_name = 'temperature'
model_name = "temperature"
child = models.ForeignKey(
'Child',
"Child",
on_delete=models.CASCADE,
related_name='temperature',
verbose_name=_('Child')
related_name="temperature",
verbose_name=_("Child"),
)
temperature = models.FloatField(
blank=False,
null=False,
verbose_name=_('Temperature')
blank=False, null=False, verbose_name=_("Temperature")
)
time = models.DateTimeField(
blank=False,
null=False,
verbose_name=_('Time')
)
notes = models.TextField(blank=True, null=True, verbose_name=_('Notes'))
time = models.DateTimeField(blank=False, null=False, verbose_name=_("Time"))
notes = models.TextField(blank=True, null=True, verbose_name=_("Notes"))
objects = models.Manager()
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-time']
verbose_name = _('Temperature')
verbose_name_plural = _('Temperature')
default_permissions = ("view", "add", "change", "delete")
ordering = ["-time"]
verbose_name = _("Temperature")
verbose_name_plural = _("Temperature")
def __str__(self):
return str(_('Temperature'))
return str(_("Temperature"))
def clean(self):
validate_time(self.time, 'time')
validate_time(self.time, "time")
class Timer(models.Model):
model_name = 'timer'
model_name = "timer"
child = models.ForeignKey(
'Child',
"Child",
blank=True,
null=True,
on_delete=models.CASCADE,
related_name='timers',
verbose_name=_('Child')
related_name="timers",
verbose_name=_("Child"),
)
name = models.CharField(
blank=True,
max_length=255,
null=True,
verbose_name=_('Name')
blank=True, max_length=255, null=True, verbose_name=_("Name")
)
start = models.DateTimeField(
default=timezone.now,
blank=False,
verbose_name=_('Start time')
default=timezone.now, blank=False, verbose_name=_("Start time")
)
end = models.DateTimeField(
blank=True,
editable=False,
null=True,
verbose_name=_('End time')
blank=True, editable=False, null=True, verbose_name=_("End time")
)
duration = models.DurationField(
editable=False,
null=True,
verbose_name=_('Duration')
)
active = models.BooleanField(
default=True,
editable=False,
verbose_name=_('Active')
editable=False, null=True, verbose_name=_("Duration")
)
active = models.BooleanField(default=True, editable=False, verbose_name=_("Active"))
user = models.ForeignKey(
'auth.User',
"auth.User",
on_delete=models.CASCADE,
related_name='timers',
verbose_name=_('User')
related_name="timers",
verbose_name=_("User"),
)
objects = models.Manager()
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-active', '-start', '-end']
verbose_name = _('Timer')
verbose_name_plural = _('Timers')
default_permissions = ("view", "add", "change", "delete")
ordering = ["-active", "-start", "-end"]
verbose_name = _("Timer")
verbose_name_plural = _("Timers")
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
def title_with_child(self):
""" Get Timer title with child name in parenthesis. """
"""Get Timer title with child name in parenthesis."""
title = str(self)
# Only actually add the name if there is more than one Child instance.
if title and self.child and Child.count() > 1:
title = format_lazy('{title} ({child})', title=title,
child=self.child)
title = format_lazy("{title} ({child})", title=title, child=self.child)
return title
@property
def user_username(self):
""" Get Timer user's name with a preference for the full name. """
"""Get Timer user's name with a preference for the full name."""
if self.user.get_full_name():
return self.user.get_full_name()
return self.user.get_username()
@ -499,51 +437,39 @@ class Timer(models.Model):
super(Timer, self).save(*args, **kwargs)
def clean(self):
validate_time(self.start, 'start')
validate_time(self.start, "start")
if self.end:
validate_time(self.end, 'end')
validate_time(self.end, "end")
validate_duration(self)
class TummyTime(models.Model):
model_name = 'tummytime'
model_name = "tummytime"
child = models.ForeignKey(
'Child',
"Child",
on_delete=models.CASCADE,
related_name='tummy_time',
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')
related_name="tummy_time",
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"))
duration = models.DurationField(
editable=False,
null=True,
verbose_name=_('Duration')
editable=False, null=True, verbose_name=_("Duration")
)
milestone = models.CharField(
blank=True,
max_length=255,
verbose_name=_('Milestone')
blank=True, max_length=255, verbose_name=_("Milestone")
)
objects = models.Manager()
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-start']
verbose_name = _('Tummy Time')
verbose_name_plural = _('Tummy Time')
default_permissions = ("view", "add", "change", "delete")
ordering = ["-start"]
verbose_name = _("Tummy Time")
verbose_name_plural = _("Tummy Time")
def __str__(self):
return str(_('Tummy Time'))
return str(_("Tummy Time"))
def save(self, *args, **kwargs):
if self.start and self.end:
@ -551,148 +477,114 @@ class TummyTime(models.Model):
super(TummyTime, self).save(*args, **kwargs)
def clean(self):
validate_time(self.start, 'start')
validate_time(self.end, 'end')
validate_time(self.start, "start")
validate_time(self.end, "end")
validate_duration(self)
validate_unique_period(
TummyTime.objects.filter(child=self.child), self)
validate_unique_period(TummyTime.objects.filter(child=self.child), self)
class Weight(models.Model):
model_name = 'weight'
model_name = "weight"
child = models.ForeignKey(
'Child',
"Child",
on_delete=models.CASCADE,
related_name='weight',
verbose_name=_('Child')
related_name="weight",
verbose_name=_("Child"),
)
weight = models.FloatField(
blank=False,
null=False,
verbose_name=_('Weight')
)
date = models.DateField(
blank=False,
null=False,
verbose_name=_('Date')
)
notes = models.TextField(blank=True, null=True, verbose_name=_('Notes'))
weight = models.FloatField(blank=False, null=False, 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()
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-date']
verbose_name = _('Weight')
verbose_name_plural = _('Weight')
default_permissions = ("view", "add", "change", "delete")
ordering = ["-date"]
verbose_name = _("Weight")
verbose_name_plural = _("Weight")
def __str__(self):
return str(_('Weight'))
return str(_("Weight"))
def clean(self):
validate_date(self.date, 'date')
validate_date(self.date, "date")
class Height(models.Model):
model_name = 'height'
model_name = "height"
child = models.ForeignKey(
'Child',
"Child",
on_delete=models.CASCADE,
related_name='height',
verbose_name=_('Child')
related_name="height",
verbose_name=_("Child"),
)
height = models.FloatField(
blank=False,
null=False,
verbose_name=_('Height')
)
date = models.DateField(
blank=False,
null=False,
verbose_name=_('Date')
)
notes = models.TextField(blank=True, null=True, verbose_name=_('Notes'))
height = models.FloatField(blank=False, null=False, 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()
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-date']
verbose_name = _('Height')
verbose_name_plural = _('Height')
default_permissions = ("view", "add", "change", "delete")
ordering = ["-date"]
verbose_name = _("Height")
verbose_name_plural = _("Height")
def __str__(self):
return str(_('Height'))
return str(_("Height"))
def clean(self):
validate_date(self.date, 'date')
validate_date(self.date, "date")
class HeadCircumference(models.Model):
model_name = 'head_circumference'
model_name = "head_circumference"
child = models.ForeignKey(
'Child',
"Child",
on_delete=models.CASCADE,
related_name='head_circumference',
verbose_name=_('Child')
related_name="head_circumference",
verbose_name=_("Child"),
)
head_circumference = models.FloatField(
blank=False,
null=False,
verbose_name=_('Head Circumference')
blank=False, null=False, verbose_name=_("Head Circumference")
)
date = models.DateField(
blank=False,
null=False,
verbose_name=_('Date')
)
notes = models.TextField(blank=True, null=True, verbose_name=_('Notes'))
date = models.DateField(blank=False, null=False, verbose_name=_("Date"))
notes = models.TextField(blank=True, null=True, verbose_name=_("Notes"))
objects = models.Manager()
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-date']
verbose_name = _('Head Circumference')
verbose_name_plural = _('Head Circumference')
default_permissions = ("view", "add", "change", "delete")
ordering = ["-date"]
verbose_name = _("Head Circumference")
verbose_name_plural = _("Head Circumference")
def __str__(self):
return str(_('Head Circumference'))
return str(_("Head Circumference"))
def clean(self):
validate_date(self.date, 'date')
validate_date(self.date, "date")
class BMI(models.Model):
model_name = 'bmi'
model_name = "bmi"
child = models.ForeignKey(
'Child',
on_delete=models.CASCADE,
related_name='bmi',
verbose_name=_('Child')
"Child", on_delete=models.CASCADE, related_name="bmi", verbose_name=_("Child")
)
bmi = models.FloatField(
blank=False,
null=False,
verbose_name=_('BMI')
)
date = models.DateField(
blank=False,
null=False,
verbose_name=_('Date')
)
notes = models.TextField(blank=True, null=True, verbose_name=_('Notes'))
bmi = models.FloatField(blank=False, null=False, 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()
class Meta:
default_permissions = ('view', 'add', 'change', 'delete')
ordering = ['-date']
verbose_name = _('BMI')
verbose_name_plural = _('BMI')
default_permissions = ("view", "add", "change", "delete")
ordering = ["-date"]
verbose_name = _("BMI")
verbose_name_plural = _("BMI")
def __str__(self):
return str(_('BMI'))
return str(_("BMI"))
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.
"""
if value:
classes = 'icon-true text-success'
classes = "icon-true text-success"
else:
classes = 'icon-false text-danger'
classes = "icon-false text-danger"
icon_html = '<i class="{}" aria-hidden="true"></i>'.format(classes)
return mark_safe(icon_html)

View File

@ -8,7 +8,7 @@ register = template.Library()
@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
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.
"""
try:
user = context['request'].user
if hasattr(user, 'settings') and user.settings.language:
user = context["request"].user
if hasattr(user, "settings") and user.settings.language:
language = user.settings.language
else:
language = settings.LANGUAGE_CODE
@ -26,17 +26,17 @@ def datetimepicker_format(context, format_string='L LT'):
language = None
if settings.USE_24_HOUR_TIME_FORMAT:
if format_string == 'L LT':
format_string = 'L HH:mm'
elif format_string == 'L LTS':
format_string = 'L HH:mm:ss'
elif language and language == 'en-GB':
if format_string == "L LT":
format_string = "L HH:mm"
elif format_string == "L LTS":
format_string = "L HH:mm:ss"
elif language and language == "en-GB":
# Force 12-hour format if 24 hour format is not configured for en-GB
# (Django default is 12H, momentjs default is 24H).
if format_string == 'L LT':
format_string = 'L h:mm a'
elif format_string == 'L LTS':
format_string = 'L h:mm:ss a'
if format_string == "L LT":
format_string = "L h:mm a"
elif format_string == "L LTS":
format_string = "L h:mm:ss a"
return format_string
@ -57,21 +57,22 @@ def datetime_short(date):
now = timezone.localtime()
if now.date() == date.date():
date_string = _('Today')
time_string = formats.date_format(date, format='TIME_FORMAT')
elif now.year == date.year and formats.get_format(
'SHORT_MONTH_DAY_FORMAT') != 'SHORT_MONTH_DAY_FORMAT':
date_string = _("Today")
time_string = formats.date_format(date, format="TIME_FORMAT")
elif (
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
# current locale.
date_string = formats.date_format(date,
format='SHORT_MONTH_DAY_FORMAT')
time_string = formats.date_format(date, format='TIME_FORMAT')
date_string = formats.date_format(date, format="SHORT_MONTH_DAY_FORMAT")
time_string = formats.date_format(date, format="TIME_FORMAT")
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:
datetime_string = _('{}, {}').format(date_string, time_string)
datetime_string = _("{}, {}").format(date_string, time_string)
else:
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`.
"""
if not birth_date:
return ''
return ""
# Return "0 days" for anything under one day.
elif timezone.localdate() - birth_date < timezone.timedelta(days=1):
return _('0 days')
return _("0 days")
try:
return timesince.timesince(birth_date, depth=1)
except (ValueError, TypeError):
return ''
return ""
@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").
:param duration: a timedetla instance.
@ -37,11 +37,11 @@ def duration_string(duration, precision='s'):
:returns: a string representation of the duration.
"""
if not duration:
return ''
return ""
try:
return utils.duration_string(duration, precision)
except (ValueError, TypeError):
return ''
return ""
@register.filter

View File

@ -8,7 +8,7 @@ from core.models import Timer
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):
"""
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.
:returns: a dictionary with timers data.
"""
request = context['request'] or None
request = context["request"] or None
timers = Timer.objects.filter(active=active)
perms = context['perms'] or None
perms = context["perms"] or None
# 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)
def instance_add_url(context, url_name):
timer = context['timer']
url = '{}?timer={}'.format(reverse(url_name), timer.id)
timer = context["timer"]
url = "{}?timer={}".format(reverse(url_name), timer.id)
if timer.child:
url += '&child={}'.format(timer.child.slug)
url += "&child={}".format(timer.child.slug)
return url

View File

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

View File

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

View File

@ -16,113 +16,119 @@ class TemplateTagsTestCase(TestCase):
def test_bootstrap_bool_icon(self):
self.assertEqual(
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(
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):
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)
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)
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)
self.assertEqual('3\xa0months', duration.child_age_string(date))
self.assertEqual("3\xa0months", duration.child_age_string(date))
def test_duration_duration_string(self):
delta = timezone.timedelta(hours=1, minutes=30, seconds=15)
self.assertEqual(
duration.duration_string(delta),
'1 hour, 30 minutes, 15 seconds')
self.assertEqual(
duration.duration_string(delta, 'm'),
'1 hour, 30 minutes')
self.assertEqual(duration.duration_string(delta, 'h'), '1 hour')
duration.duration_string(delta), "1 hour, 30 minutes, 15 seconds"
)
self.assertEqual(duration.duration_string(delta, "m"), "1 hour, 30 minutes")
self.assertEqual(duration.duration_string(delta, "h"), "1 hour")
self.assertEqual(duration.duration_string(''), '')
self.assertRaises(TypeError, duration.duration_string('not a delta'))
self.assertEqual(duration.duration_string(""), "")
self.assertRaises(TypeError, duration.duration_string("not a delta"))
def test_duration_hours(self):
delta = timezone.timedelta(hours=1)
self.assertEqual(duration.hours(delta), 1)
self.assertEqual(duration.hours(''), 0)
self.assertRaises(TypeError, duration.hours('not a delta'))
self.assertEqual(duration.hours(""), 0)
self.assertRaises(TypeError, duration.hours("not a delta"))
def test_duration_minutes(self):
delta = timezone.timedelta(minutes=45)
self.assertEqual(duration.minutes(delta), 45)
self.assertEqual(duration.minutes(''), 0)
self.assertRaises(TypeError, duration.minutes('not a delta'))
self.assertEqual(duration.minutes(""), 0)
self.assertRaises(TypeError, duration.minutes("not a delta"))
def test_duration_seconds(self):
delta = timezone.timedelta(seconds=20)
self.assertEqual(duration.seconds(delta), 20)
self.assertEqual(duration.seconds(''), 0)
self.assertRaises(TypeError, duration.seconds('not a delta'))
self.assertEqual(duration.seconds(""), 0)
self.assertRaises(TypeError, duration.seconds("not a delta"))
def test_instance_add_url(self):
child = Child.objects.create(first_name='Test', last_name='Child',
birth_date=timezone.localdate())
user = User.objects.create_user(username='timer')
child = Child.objects.create(
first_name="Test", last_name="Child", birth_date=timezone.localdate()
)
user = User.objects.create_user(username="timer")
timer = Timer.objects.create(user=user)
url = timers.instance_add_url({'timer': timer}, 'core:sleep-add')
self.assertEqual(url, '/sleep/add/?timer={}'.format(timer.id))
url = timers.instance_add_url({"timer": timer}, "core:sleep-add")
self.assertEqual(url, "/sleep/add/?timer={}".format(timer.id))
timer = Timer.objects.create(user=user, child=child)
url = timers.instance_add_url({'timer': timer}, 'core:sleep-add')
self.assertEqual(url, '/sleep/add/?timer={}&child={}'.format(
timer.id, child.slug))
url = timers.instance_add_url({"timer": timer}, "core:sleep-add")
self.assertEqual(
url, "/sleep/add/?timer={}&child={}".format(timer.id, child.slug)
)
def test_datetimepicker_format(self):
request = MockUserRequest(User.objects.first())
request.user.settings.dashboard_hide_empty = True
context = {'request': request}
context = {"request": request}
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'), 'L LT')
self.assertEqual(
datetime.datetimepicker_format(context, 'L LTS'), 'L LTS')
self.assertEqual(datetime.datetimepicker_format(context), "L LT")
self.assertEqual(datetime.datetimepicker_format(context, "L LT"), "L LT")
self.assertEqual(datetime.datetimepicker_format(context, "L LTS"), "L LTS")
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(
datetime.datetimepicker_format(context), 'L HH:mm')
self.assertEqual(
datetime.datetimepicker_format(context, 'L LT'), 'L HH:mm')
self.assertEqual(
datetime.datetimepicker_format(context, 'L LTS'), 'L HH:mm:ss')
datetime.datetimepicker_format(context, "L LTS"), "L HH:mm:ss"
)
@override_settings(USE_24_HOUR_TIME_FORMAT=False)
def test_datetimepicker_format_en_gb(self):
user = User.objects.first()
user.settings.language = 'en-GB'
user.settings.language = "en-GB"
user.save()
request = MockUserRequest(user)
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(
datetime.datetimepicker_format(context), 'L h:mm 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')
datetime.datetimepicker_format(context, "L LTS"), "L h:mm:ss a"
)
def test_datetime_short(self):
date = timezone.localtime()
self.assertEqual(datetime.datetime_short(date), "Today, {}".format(
formats.date_format(date, format='TIME_FORMAT')))
self.assertEqual(
datetime.datetime_short(date),
"Today, {}".format(formats.date_format(date, format="TIME_FORMAT")),
)
date = timezone.localtime() - timezone.timedelta(days=1, hours=6)
self.assertEqual(datetime.datetime_short(date), "{}, {}".format(
formats.date_format(date, format='SHORT_MONTH_DAY_FORMAT'),
formats.date_format(date, format='TIME_FORMAT')))
self.assertEqual(
datetime.datetime_short(date),
"{}, {}".format(
formats.date_format(date, format="SHORT_MONTH_DAY_FORMAT"),
formats.date_format(date, format="TIME_FORMAT"),
),
)
date = timezone.localtime() - timezone.timedelta(days=500)
self.assertEqual(datetime.datetime_short(date), formats.date_format(
date, format='SHORT_DATETIME_FORMAT'))
self.assertEqual(
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):
def test_duration_string(self):
duration = timezone.timedelta(hours=1, minutes=30, seconds=45)
self.assertEqual(
duration_string(duration),
'1 hour, 30 minutes, 45 seconds')
self.assertEqual(duration_string(duration, 'm'), '1 hour, 30 minutes')
self.assertEqual(duration_string(duration, 'h'), '1 hour')
self.assertRaises(TypeError, lambda: duration_string('1 hour'))
self.assertEqual(duration_string(duration), "1 hour, 30 minutes, 45 seconds")
self.assertEqual(duration_string(duration, "m"), "1 hour, 30 minutes")
self.assertEqual(duration_string(duration, "h"), "1 hour")
self.assertRaises(TypeError, lambda: duration_string("1 hour"))
def test_duration_parts(self):
duration = timezone.timedelta(hours=1, minutes=30, seconds=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):
super(ViewsTestCase, cls).setUpClass()
fake = Factory.create()
call_command('migrate', verbosity=0)
call_command('fake', verbosity=0)
call_command("migrate", verbosity=0)
call_command("fake", verbosity=0)
cls.c = HttpClient()
fake_user = fake.simple_profile()
cls.credentials = {
'username': fake_user['username'],
'password': fake.password()
"username": fake_user["username"],
"password": fake.password(),
}
cls.user = User.objects.create_user(
is_superuser=True, **cls.credentials)
cls.user = User.objects.create_user(is_superuser=True, **cls.credentials)
cls.c.login(**cls.credentials)
def test_child_views(self):
page = self.c.get('/children/')
page = self.c.get("/children/")
self.assertEqual(page.status_code, 200)
page = self.c.get('/children/add/')
page = self.c.get("/children/add/")
self.assertEqual(page.status_code, 200)
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)
page = self.c.get(
'/children/{}/'.format(entry.slug),
{'date': timezone.localdate() - timezone.timedelta(days=1)})
"/children/{}/".format(entry.slug),
{"date": timezone.localdate() - timezone.timedelta(days=1)},
)
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)
page = self.c.get('/children/{}/delete/'.format(entry.slug))
page = self.c.get("/children/{}/delete/".format(entry.slug))
self.assertEqual(page.status_code, 200)
def test_diaperchange_views(self):
page = self.c.get('/changes/')
page = self.c.get("/changes/")
self.assertEqual(page.status_code, 200)
page = self.c.get('/changes/add/')
page = self.c.get("/changes/add/")
self.assertEqual(page.status_code, 200)
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)
page = self.c.get('/changes/{}/delete/'.format(entry.id))
page = self.c.get("/changes/{}/delete/".format(entry.id))
self.assertEqual(page.status_code, 200)
def test_feeding_views(self):
page = self.c.get('/feedings/')
page = self.c.get("/feedings/")
self.assertEqual(page.status_code, 200)
page = self.c.get('/feedings/add/')
page = self.c.get("/feedings/add/")
self.assertEqual(page.status_code, 200)
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)
page = self.c.get('/feedings/{}/delete/'.format(entry.id))
page = self.c.get("/feedings/{}/delete/".format(entry.id))
self.assertEqual(page.status_code, 200)
def test_note_views(self):
page = self.c.get('/notes/')
page = self.c.get("/notes/")
self.assertEqual(page.status_code, 200)
page = self.c.get('/notes/add/')
page = self.c.get("/notes/add/")
self.assertEqual(page.status_code, 200)
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)
page = self.c.get('/notes/{}/delete/'.format(entry.id))
page = self.c.get("/notes/{}/delete/".format(entry.id))
self.assertEqual(page.status_code, 200)
def test_sleep_views(self):
page = self.c.get('/sleep/')
page = self.c.get("/sleep/")
self.assertEqual(page.status_code, 200)
page = self.c.get('/sleep/add/')
page = self.c.get("/sleep/add/")
self.assertEqual(page.status_code, 200)
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)
page = self.c.get('/sleep/{}/delete/'.format(entry.id))
page = self.c.get("/sleep/{}/delete/".format(entry.id))
self.assertEqual(page.status_code, 200)
def test_temperature_views(self):
page = self.c.get('/temperature/')
page = self.c.get("/temperature/")
self.assertEqual(page.status_code, 200)
page = self.c.get('/temperature/add/')
page = self.c.get("/temperature/add/")
self.assertEqual(page.status_code, 200)
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)
page = self.c.get('/temperature/{}/delete/'.format(entry.id))
page = self.c.get("/temperature/{}/delete/".format(entry.id))
self.assertEqual(page.status_code, 200)
def test_timer_views(self):
page = self.c.get('/timers/')
page = self.c.get("/timers/")
self.assertEqual(page.status_code, 200)
page = self.c.get('/timers/add/')
page = self.c.get("/timers/add/")
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)
page = self.c.post('/timers/add/quick/', follow=True)
page = self.c.post("/timers/add/quick/", follow=True)
self.assertEqual(page.status_code, 200)
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)
page = self.c.get('/timers/{}/edit/'.format(entry.id))
page = self.c.get("/timers/{}/edit/".format(entry.id))
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)
page = self.c.get('/timers/{}/stop/'.format(entry.id))
page = self.c.get("/timers/{}/stop/".format(entry.id))
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)
page = self.c.get('/timers/{}/restart/'.format(entry.id))
page = self.c.get("/timers/{}/restart/".format(entry.id))
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)
page = self.c.get('/timers/delete-inactive/', follow=True)
page = self.c.get("/timers/delete-inactive/", follow=True)
self.assertEqual(page.status_code, 200)
messages = list(page.context['messages'])
messages = list(page.context["messages"])
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.stop()
page = self.c.get('/timers/delete-inactive/')
page = self.c.get("/timers/delete-inactive/")
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):
child = models.Child.objects.first()
response = self.c.get('/timeline/')
self.assertRedirects(response, '/children/{}/'.format(child.slug))
response = self.c.get("/timeline/")
self.assertRedirects(response, "/children/{}/".format(child.slug))
models.Child.objects.create(
first_name='Second',
last_name='Child',
birth_date='2000-01-01'
first_name="Second", last_name="Child", birth_date="2000-01-01"
)
response = self.c.get('/timeline/')
response = self.c.get("/timeline/")
self.assertEqual(response.status_code, 200)
def test_tummytime_views(self):
page = self.c.get('/tummy-time/')
page = self.c.get("/tummy-time/")
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)
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)
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)
def test_weight_views(self):
page = self.c.get('/weight/')
page = self.c.get("/weight/")
self.assertEqual(page.status_code, 200)
page = self.c.get('/weight/add/')
page = self.c.get("/weight/add/")
self.assertEqual(page.status_code, 200)
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)
page = self.c.get('/weight/{}/delete/'.format(entry.id))
page = self.c.get("/weight/{}/delete/".format(entry.id))
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_notes(min_date, max_date, events, child)
explicit_type_ordering = {'start': 0, 'end': 1}
explicit_type_ordering = {"start": 0, "end": 1}
events.sort(
key=lambda x: (
x['time'],
explicit_type_ordering.get(x.get('type'), -1),
x["time"],
explicit_type_ordering.get(x.get("type"), -1),
),
reverse=True,
)
@ -37,69 +37,74 @@ def get_objects(date, child=None):
def _add_tummy_times(min_date, max_date, events, child=None):
instances = TummyTime.objects.filter(
start__range=(min_date, max_date)).order_by('-start')
instances = TummyTime.objects.filter(start__range=(min_date, max_date)).order_by(
"-start"
)
if child:
instances = instances.filter(child=child)
for instance in instances:
details = []
if instance.milestone:
details.append(instance.milestone)
edit_link = reverse('core:tummytime-update', args=[instance.id])
events.append({
'time': timezone.localtime(instance.start),
'event': _('%(child)s started tummy time!') % {
'child': instance.child.first_name
},
'details': details,
'edit_link': edit_link,
'model_name': instance.model_name,
'type': 'start'
})
events.append({
'time': timezone.localtime(instance.end),
'event': _('%(child)s finished tummy time.') % {
'child': instance.child.first_name
},
'details': details,
'edit_link': edit_link,
'duration': timesince.timesince(instance.start, now=instance.end),
'model_name': instance.model_name,
'type': 'end'
})
edit_link = reverse("core:tummytime-update", args=[instance.id])
events.append(
{
"time": timezone.localtime(instance.start),
"event": _("%(child)s started tummy time!")
% {"child": instance.child.first_name},
"details": details,
"edit_link": edit_link,
"model_name": instance.model_name,
"type": "start",
}
)
events.append(
{
"time": timezone.localtime(instance.end),
"event": _("%(child)s finished tummy time.")
% {"child": instance.child.first_name},
"details": details,
"edit_link": edit_link,
"duration": timesince.timesince(instance.start, now=instance.end),
"model_name": instance.model_name,
"type": "end",
}
)
def _add_sleeps(min_date, max_date, events, child=None):
instances = Sleep.objects.filter(
start__range=(min_date, max_date)).order_by('-start')
instances = Sleep.objects.filter(start__range=(min_date, max_date)).order_by(
"-start"
)
if child:
instances = instances.filter(child=child)
for instance in instances:
details = []
if instance.notes:
details.append(instance.notes)
edit_link = reverse('core:sleep-update', args=[instance.id])
events.append({
'time': timezone.localtime(instance.start),
'event': _('%(child)s fell asleep.') % {
'child': instance.child.first_name
},
'details': details,
'edit_link': edit_link,
'model_name': instance.model_name,
'type': 'start'
})
events.append({
'time': timezone.localtime(instance.end),
'event': _('%(child)s woke up.') % {
'child': instance.child.first_name
},
'details': details,
'edit_link': edit_link,
'duration': timesince.timesince(instance.start, now=instance.end),
'model_name': instance.model_name,
'type': 'end'
})
edit_link = reverse("core:sleep-update", args=[instance.id])
events.append(
{
"time": timezone.localtime(instance.start),
"event": _("%(child)s fell asleep.")
% {"child": instance.child.first_name},
"details": details,
"edit_link": edit_link,
"model_name": instance.model_name,
"type": "start",
}
)
events.append(
{
"time": timezone.localtime(instance.end),
"event": _("%(child)s woke up.") % {"child": instance.child.first_name},
"details": details,
"edit_link": edit_link,
"duration": timesince.timesince(instance.start, now=instance.end),
"model_name": instance.model_name,
"type": "end",
}
)
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)
prev_start = None
instances = Feeding.objects.filter(
start__range=(yesterday, max_date)).order_by('start')
instances = Feeding.objects.filter(start__range=(yesterday, max_date)).order_by(
"start"
)
if child:
instances = instances.filter(child=child)
for instance in instances:
@ -117,73 +123,80 @@ def _add_feedings(min_date, max_date, events, child=None):
details.append(instance.notes)
time_since_prev = None
if prev_start:
time_since_prev = \
timesince.timesince(prev_start, now=instance.start)
time_since_prev = timesince.timesince(prev_start, now=instance.start)
prev_start = instance.start
if instance.start < min_date:
continue
edit_link = reverse('core:feeding-update', args=[instance.id])
edit_link = reverse("core:feeding-update", args=[instance.id])
if instance.amount:
details.append(_('Amount: %(amount).0f') % {
'amount': instance.amount,
})
events.append({
'time': timezone.localtime(instance.start),
'event': _('%(child)s started feeding.') % {
'child': instance.child.first_name
},
'details': details,
'edit_link': edit_link,
'time_since_prev': time_since_prev,
'model_name': instance.model_name,
'type': 'start'
})
events.append({
'time': timezone.localtime(instance.end),
'event': _('%(child)s finished feeding.') % {
'child': instance.child.first_name
},
'details': details,
'edit_link': edit_link,
'duration': timesince.timesince(instance.start, now=instance.end),
'model_name': instance.model_name,
'type': 'end'
})
details.append(
_("Amount: %(amount).0f")
% {
"amount": instance.amount,
}
)
events.append(
{
"time": timezone.localtime(instance.start),
"event": _("%(child)s started feeding.")
% {"child": instance.child.first_name},
"details": details,
"edit_link": edit_link,
"time_since_prev": time_since_prev,
"model_name": instance.model_name,
"type": "start",
}
)
events.append(
{
"time": timezone.localtime(instance.end),
"event": _("%(child)s finished feeding.")
% {"child": instance.child.first_name},
"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):
instances = DiaperChange.objects.filter(
time__range=(min_date, max_date)).order_by('-time')
instances = DiaperChange.objects.filter(time__range=(min_date, max_date)).order_by(
"-time"
)
if child:
instances = instances.filter(child=child)
for instance in instances:
contents = []
if instance.wet:
contents.append('💧')
contents.append("💧")
if instance.solid:
contents.append('💩')
events.append({
'time': timezone.localtime(instance.time),
'event': _('%(child)s had a %(type)s diaper change.') % {
'child': instance.child.first_name,
'type': ''.join(contents),
contents.append("💩")
events.append(
{
"time": timezone.localtime(instance.time),
"event": _("%(child)s had a %(type)s diaper change.")
% {
"child": instance.child.first_name,
"type": "".join(contents),
},
'edit_link': reverse('core:diaperchange-update',
args=[instance.id]),
'model_name': instance.model_name
})
"edit_link": reverse("core:diaperchange-update", args=[instance.id]),
"model_name": instance.model_name,
}
)
def _add_notes(min_date, max_date, events, child):
instances = Note.objects.filter(
time__range=(min_date, max_date)).order_by('-time')
instances = Note.objects.filter(time__range=(min_date, max_date)).order_by("-time")
if child:
instances = instances.filter(child=child)
for instance in instances:
events.append({
'time': timezone.localtime(instance.time),
'details': [instance.note],
'edit_link': reverse('core:note-update',
args=[instance.id]),
'model_name': instance.model_name
})
events.append(
{
"time": timezone.localtime(instance.time),
"details": [instance.note],
"edit_link": reverse("core:note-update", args=[instance.id]),
"model_name": instance.model_name,
}
)

View File

@ -3,206 +3,112 @@ from django.urls import path
from . import views
app_name = 'core'
app_name = "core"
urlpatterns = [
path('children/', views.ChildList.as_view(), name='child-list'),
path('children/add/', views.ChildAdd.as_view(), name='child-add'),
path('children/<str:slug>/', views.ChildDetail.as_view(), name='child'),
path("children/", views.ChildList.as_view(), name="child-list"),
path("children/add/", views.ChildAdd.as_view(), name="child-add"),
path("children/<str:slug>/", views.ChildDetail.as_view(), name="child"),
path("children/<str:slug>/edit/", views.ChildUpdate.as_view(), name="child-update"),
path(
'children/<str:slug>/edit/',
views.ChildUpdate.as_view(),
name='child-update'
"children/<str:slug>/delete/", 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(
'children/<str:slug>/delete/',
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>/',
"changes/<int:pk>/",
views.DiaperChangeUpdate.as_view(),
name='diaperchange-update'
name="diaperchange-update",
),
path(
'changes/<int:pk>/delete/',
"changes/<int:pk>/delete/",
views.DiaperChangeDelete.as_view(),
name='diaperchange-delete'
name="diaperchange-delete",
),
path('feedings/', views.FeedingList.as_view(), name='feeding-list'),
path('feedings/add/', views.FeedingAdd.as_view(), name='feeding-add'),
path("feedings/", views.FeedingList.as_view(), name="feeding-list"),
path("feedings/add/", views.FeedingAdd.as_view(), name="feeding-add"),
path("feedings/<int:pk>/", views.FeedingUpdate.as_view(), name="feeding-update"),
path(
'feedings/<int:pk>/',
views.FeedingUpdate.as_view(),
name='feeding-update'
),
path(
'feedings/<int:pk>/delete/',
"feedings/<int:pk>/delete/",
views.FeedingDelete.as_view(),
name='feeding-delete'
name="feeding-delete",
),
path('notes/', views.NoteList.as_view(), name='note-list'),
path('notes/add/', views.NoteAdd.as_view(), name='note-add'),
path('notes/<int:pk>/', views.NoteUpdate.as_view(), name='note-update'),
path("notes/", views.NoteList.as_view(), name="note-list"),
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>/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(
'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(
'temperature/<int:pk>/',
"temperature/<int:pk>/",
views.TemperatureUpdate.as_view(),
name='temperature-update'
name="temperature-update",
),
path(
'temperature/<int:pk>/delete/',
"temperature/<int:pk>/delete/",
views.TemperatureDelete.as_view(),
name='temperature-delete'
name="temperature-delete",
),
path('timers/', views.TimerList.as_view(), name='timer-list'),
path('timers/add/', views.TimerAdd.as_view(), name='timer-add'),
path("timers/", views.TimerList.as_view(), name="timer-list"),
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(
'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'
"timers/<int:pk>/restart/", views.TimerRestart.as_view(), name="timer-restart"
),
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(
'timers/<int:pk>/restart/',
views.TimerRestart.as_view(),
name='timer-restart'
),
path(
'timers/delete-inactive/',
"timers/delete-inactive/",
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(
'tummy-time/add/',
views.TummyTimeAdd.as_view(),
name='tummytime-add'
"tummy-time/<int:pk>/", views.TummyTimeUpdate.as_view(), name="tummytime-update"
),
path(
'tummy-time/<int:pk>/',
views.TummyTimeUpdate.as_view(),
name='tummytime-update'
),
path(
'tummy-time/<int:pk>/delete/',
"tummy-time/<int:pk>/delete/",
views.TummyTimeDelete.as_view(),
name='tummytime-delete'
name="tummytime-delete",
),
path('weight/', views.WeightList.as_view(), name='weight-list'),
path('weight/add/', views.WeightAdd.as_view(), name='weight-add'),
path("weight/", views.WeightList.as_view(), name="weight-list"),
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(
'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(
'head-circumference/',
"head-circumference/",
views.HeadCircumferenceList.as_view(),
name='head-circumference-list'
name="head-circumference-list",
),
path(
'head-circumference/add/',
"head-circumference/add/",
views.HeadCircumferenceAdd.as_view(),
name='head-circumference-add'
name="head-circumference-add",
),
path(
'head-circumference/<int:pk>/',
"head-circumference/<int:pk>/",
views.HeadCircumferenceUpdate.as_view(),
name='head-circumference-update'
name="head-circumference-update",
),
path(
'head-circumference/<int:pk>/delete/',
"head-circumference/<int:pk>/delete/",
views.HeadCircumferenceDelete.as_view(),
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'
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"),
]

View File

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

View File

@ -13,39 +13,42 @@ register = template.Library()
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"):
filter = {}
if context['request'].user.settings.dashboard_hide_age:
if context["request"].user.settings.dashboard_hide_age:
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)
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):
"""
Information about the most recent diaper change.
:param child: an instance of the Child model.
:returns: a dictionary with the most recent Diaper Change instance.
"""
instance = models.DiaperChange.objects.filter(child=child) \
.filter(**_filter_data_age(context, "time")) \
.order_by('-time').first()
instance = (
models.DiaperChange.objects.filter(child=child)
.filter(**_filter_data_age(context, "time"))
.order_by("-time")
.first()
)
empty = not instance
return {
'type': 'diaperchange',
'change': instance,
'empty': empty,
'hide_empty': _hide_empty(context)
"type": "diaperchange",
"change": instance,
"empty": empty,
"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):
"""
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)
stats = {}
week_total = 0
max_date = (time + timezone.timedelta(days=1)).replace(
hour=0, minute=0, second=0)
max_date = (time + timezone.timedelta(days=1)).replace(hour=0, minute=0, second=0)
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):
stats[x] = {'wet': 0.0, 'solid': 0.0}
stats[x] = {"wet": 0.0, "solid": 0.0}
instances = models.DiaperChange.objects.filter(child=child) \
.filter(time__gt=min_date).filter(time__lt=max_date).order_by('-time')
instances = (
models.DiaperChange.objects.filter(child=child)
.filter(time__gt=min_date)
.filter(time__lt=max_date)
.order_by("-time")
)
empty = len(instances) == 0
for instance in instances:
key = (max_date - instance.time).days
if instance.wet:
stats[key]['wet'] += 1
stats[key]["wet"] += 1
if instance.solid:
stats[key]['solid'] += 1
stats[key]["solid"] += 1
for key, info in stats.items():
total = info['wet'] + info['solid']
total = info["wet"] + info["solid"]
week_total += total
if total > 0:
stats[key]['wet_pct'] = info['wet'] / total * 100
stats[key]['solid_pct'] = info['solid'] / total * 100
stats[key]["wet_pct"] = info["wet"] / total * 100
stats[key]["solid_pct"] = info["solid"] / total * 100
return {
'type': 'diaperchange',
'stats': stats,
'total': week_total,
'empty': empty,
'hide_empty': _hide_empty(context)
"type": "diaperchange",
"stats": stats,
"total": week_total,
"empty": empty,
"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):
"""
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()
instances = models.Feeding.objects.filter(child=child).filter(
start__year=date.year,
start__month=date.month,
start__day=date.day) \
| models.Feeding.objects.filter(child=child).filter(
end__year=date.year,
end__month=date.month,
end__day=date.day)
start__year=date.year, start__month=date.month, start__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])
count = len(instances)
empty = len(instances) == 0 or total == 0
return {
'type': 'feeding',
'total': total,
'count': count,
'empty': empty,
'hide_empty': _hide_empty(context)
"type": "feeding",
"total": total,
"count": count,
"empty": empty,
"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):
"""
Information about the most recent feeding.
:param child: an instance of the Child model.
:returns: a dictionary with the most recent Feeding instance.
"""
instance = models.Feeding.objects.filter(child=child) \
.filter(**_filter_data_age(context)) \
.order_by('-end').first()
instance = (
models.Feeding.objects.filter(child=child)
.filter(**_filter_data_age(context))
.order_by("-end")
.first()
)
empty = not instance
return {
'type': 'feeding',
'feeding': instance,
'empty': empty,
'hide_empty': _hide_empty(context)
"type": "feeding",
"feeding": instance,
"empty": empty,
"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):
"""
Information about the three most recent feeding methods.
:param child: an instance of the Child model.
:returns: a dictionary with the most recent Feeding instances.
"""
instances = models.Feeding.objects.filter(child=child) \
.filter(**_filter_data_age(context)) \
.order_by('-end')[:3]
instances = (
models.Feeding.objects.filter(child=child)
.filter(**_filter_data_age(context))
.order_by("-end")[:3]
)
num_unique_methods = len({i.method for i in instances})
empty = num_unique_methods <= 1
# Results are reversed for carousel forward/back behavior.
return {
'type': 'feeding',
'feedings': list(reversed(instances)),
'empty': empty,
'hide_empty': _hide_empty(context)
"type": "feeding",
"feedings": list(reversed(instances)),
"empty": empty,
"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):
"""
Information about the most recent sleep entry.
:param child: an instance of the Child model.
:returns: a dictionary with the most recent Sleep instance.
"""
instance = models.Sleep.objects.filter(child=child) \
.filter(**_filter_data_age(context)) \
.order_by('-end').first()
instance = (
models.Sleep.objects.filter(child=child)
.filter(**_filter_data_age(context))
.order_by("-end")
.first()
)
empty = not instance
return {
'type': 'sleep',
'sleep': instance,
'empty': empty,
'hide_empty': _hide_empty(context)
"type": "sleep",
"sleep": instance,
"empty": empty,
"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):
"""
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:
date = timezone.localtime().date()
instances = models.Sleep.objects.filter(child=child).filter(
start__year=date.year,
start__month=date.month,
start__day=date.day) | models.Sleep.objects.filter(child=child).filter(
end__year=date.year,
end__month=date.month,
end__day=date.day)
start__year=date.year, start__month=date.month, start__day=date.day
) | models.Sleep.objects.filter(child=child).filter(
end__year=date.year, end__month=date.month, end__day=date.day
)
empty = len(instances) == 0
total = timezone.timedelta(seconds=0)
@ -216,23 +226,24 @@ def card_sleep_day(context, child, date=None):
end = timezone.localtime(instance.end)
# Account for dates crossing midnight.
if start.date() != date:
start = start.replace(year=end.year, month=end.month, day=end.day,
hour=0, minute=0, second=0)
start = start.replace(
year=end.year, month=end.month, day=end.day, hour=0, minute=0, second=0
)
total += end - start
count = len(instances)
return {
'type': 'sleep',
'total': total,
'count': count,
'empty': empty,
'hide_empty': _hide_empty(context)
"type": "sleep",
"total": total,
"count": count,
"empty": empty,
"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):
"""
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:
date = timezone.localtime().date()
instances = models.Sleep.naps.filter(child=child).filter(
start__year=date.year,
start__month=date.month,
start__day=date.day) | models.Sleep.naps.filter(child=child).filter(
end__year=date.year,
end__month=date.month,
end__day=date.day)
start__year=date.year, start__month=date.month, start__day=date.day
) | models.Sleep.naps.filter(child=child).filter(
end__year=date.year, end__month=date.month, end__day=date.day
)
empty = len(instances) == 0
return {
'type': 'sleep',
'total': instances.aggregate(Sum('duration'))['duration__sum'],
'count': len(instances),
'empty': empty, 'hide_empty': _hide_empty(context)
"type": "sleep",
"total": instances.aggregate(Sum("duration"))["duration__sum"],
"count": len(instances),
"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):
"""
Statistics data for all models.
@ -271,76 +281,102 @@ def card_statistics(context, child):
changes = _diaperchange_statistics(child)
if changes:
stats.append({
'type': 'duration',
'stat': changes['btwn_average'],
'title': _('Diaper change frequency')})
stats.append(
{
"type": "duration",
"stat": changes["btwn_average"],
"title": _("Diaper change frequency"),
}
)
feedings = _feeding_statistics(child)
if feedings:
for item in feedings:
stats.append({
'type': 'duration',
'stat': item['btwn_average'],
'title': item['title']})
stats.append(
{
"type": "duration",
"stat": item["btwn_average"],
"title": item["title"],
}
)
naps = _nap_statistics(child)
if naps:
stats.append({
'type': 'duration',
'stat': naps['average'],
'title': _('Average nap duration')})
stats.append({
'type': 'float',
'stat': naps['avg_per_day'],
'title': _('Average naps per day')})
stats.append(
{
"type": "duration",
"stat": naps["average"],
"title": _("Average nap duration"),
}
)
stats.append(
{
"type": "float",
"stat": naps["avg_per_day"],
"title": _("Average naps per day"),
}
)
sleep = _sleep_statistics(child)
if sleep:
stats.append({
'type': 'duration',
'stat': sleep['average'],
'title': _('Average sleep duration')})
stats.append({
'type': 'duration',
'stat': sleep['btwn_average'],
'title': _('Average awake duration')})
stats.append(
{
"type": "duration",
"stat": sleep["average"],
"title": _("Average sleep duration"),
}
)
stats.append(
{
"type": "duration",
"stat": sleep["btwn_average"],
"title": _("Average awake duration"),
}
)
weight = _weight_statistics(child)
if weight:
stats.append({
'type': 'float',
'stat': weight['change_weekly'],
'title': _('Weight change per week')})
stats.append(
{
"type": "float",
"stat": weight["change_weekly"],
"title": _("Weight change per week"),
}
)
height = _height_statistics(child)
if height:
stats.append({
'type': 'float',
'stat': height['change_weekly'],
'title': _('Height change per week')})
stats.append(
{
"type": "float",
"stat": height["change_weekly"],
"title": _("Height change per week"),
}
)
head_circumference = _head_circumference_statistics(child)
if head_circumference:
stats.append({
'type': 'float',
'stat': head_circumference['change_weekly'],
'title': _('Head circumference change per week')})
stats.append(
{
"type": "float",
"stat": head_circumference["change_weekly"],
"title": _("Head circumference change per week"),
}
)
bmi = _bmi_statistics(child)
if bmi:
stats.append({
'type': 'float',
'stat': bmi['change_weekly'],
'title': _('BMI change per week')})
stats.append(
{
"type": "float",
"stat": bmi["change_weekly"],
"title": _("BMI change per week"),
}
)
empty = len(stats) == 0
return {
'stats': stats,
'empty': empty,
'hide_empty': _hide_empty(context)
}
return {"stats": stats, "empty": empty, "hide_empty": _hide_empty(context)}
def _diaperchange_statistics(child):
@ -349,23 +385,23 @@ def _diaperchange_statistics(child):
:param child: an instance of the Child model.
:returns: a dictionary of statistics.
"""
instances = models.DiaperChange.objects.filter(child=child) \
.order_by('time')
instances = models.DiaperChange.objects.filter(child=child).order_by("time")
if len(instances) == 0:
return False
changes = {
'btwn_total': timezone.timedelta(0),
'btwn_count': instances.count() - 1,
'btwn_average': 0.0}
"btwn_total": timezone.timedelta(0),
"btwn_count": instances.count() - 1,
"btwn_average": 0.0,
}
last_instance = None
for instance in instances:
if last_instance:
changes['btwn_total'] += instance.time - last_instance.time
changes["btwn_total"] += instance.time - last_instance.time
last_instance = instance
if changes['btwn_count'] > 0:
changes['btwn_average'] = changes['btwn_total'] / changes['btwn_count']
if changes["btwn_count"] > 0:
changes["btwn_average"] = changes["btwn_total"] / changes["btwn_count"]
return changes
@ -378,26 +414,26 @@ def _feeding_statistics(child):
"""
feedings = [
{
'start': timezone.now() - timezone.timedelta(days=3),
'title': _('Feeding frequency (past 3 days)')
"start": timezone.now() - timezone.timedelta(days=3),
"title": _("Feeding frequency (past 3 days)"),
},
{
'start': timezone.now() - timezone.timedelta(weeks=2),
'title': _('Feeding frequency (past 2 weeks)')
"start": timezone.now() - timezone.timedelta(weeks=2),
"title": _("Feeding frequency (past 2 weeks)"),
},
{
'start': timezone.make_aware(
datetime.combine(date.min, time(0, 0))
+ timezone.timedelta(days=1)),
'title': _('Feeding frequency')
}
"start": timezone.make_aware(
datetime.combine(date.min, time(0, 0)) + timezone.timedelta(days=1)
),
"title": _("Feeding frequency"),
},
]
for timespan in feedings:
timespan['btwn_total'] = timezone.timedelta(0)
timespan['btwn_count'] = 0
timespan['btwn_average'] = 0.0
timespan["btwn_total"] = timezone.timedelta(0)
timespan["btwn_count"] = 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:
return False
last_instance = None
@ -405,16 +441,14 @@ def _feeding_statistics(child):
for instance in instances:
if last_instance:
for timespan in feedings:
if last_instance.start > timespan['start']:
timespan['btwn_total'] += (instance.start
- last_instance.end)
timespan['btwn_count'] += 1
if last_instance.start > timespan["start"]:
timespan["btwn_total"] += instance.start - last_instance.end
timespan["btwn_count"] += 1
last_instance = instance
for timespan in feedings:
if timespan['btwn_count'] > 0:
timespan['btwn_average'] = \
timespan['btwn_total'] / timespan['btwn_count']
if timespan["btwn_count"] > 0:
timespan["btwn_average"] = timespan["btwn_total"] / timespan["btwn_count"]
return feedings
@ -424,21 +458,26 @@ def _nap_statistics(child):
:param child: an instance of the Child model.
: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:
return False
naps = {
'total': instances.aggregate(Sum('duration'))['duration__sum'],
'count': instances.count(),
'average': 0.0,
'avg_per_day': 0.0}
if naps['count'] > 0:
naps['average'] = naps['total'] / naps['count']
"total": instances.aggregate(Sum("duration"))["duration__sum"],
"count": instances.count(),
"average": 0.0,
"avg_per_day": 0.0,
}
if naps["count"] > 0:
naps["average"] = naps["total"] / naps["count"]
naps_avg = instances.annotate(date=TruncDate('start')).values('date') \
.annotate(naps_count=Count('id')).order_by() \
.aggregate(Avg('naps_count'))
naps['avg_per_day'] = naps_avg['naps_count__avg']
naps_avg = (
instances.annotate(date=TruncDate("start"))
.values("date")
.annotate(naps_count=Count("id"))
.order_by()
.aggregate(Avg("naps_count"))
)
naps["avg_per_day"] = naps_avg["naps_count__avg"]
return naps
@ -449,28 +488,29 @@ def _sleep_statistics(child):
:param child: an instance of the Child model.
: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:
return False
sleep = {
'total': instances.aggregate(Sum('duration'))['duration__sum'],
'count': instances.count(),
'average': 0.0,
'btwn_total': timezone.timedelta(0),
'btwn_count': instances.count() - 1,
'btwn_average': 0.0}
"total": instances.aggregate(Sum("duration"))["duration__sum"],
"count": instances.count(),
"average": 0.0,
"btwn_total": timezone.timedelta(0),
"btwn_count": instances.count() - 1,
"btwn_average": 0.0,
}
last_instance = None
for instance in instances:
if last_instance:
sleep['btwn_total'] += instance.start - last_instance.end
sleep["btwn_total"] += instance.start - last_instance.end
last_instance = instance
if sleep['count'] > 0:
sleep['average'] = sleep['total'] / sleep['count']
if sleep['btwn_count'] > 0:
sleep['btwn_average'] = sleep['btwn_total'] / sleep['btwn_count']
if sleep["count"] > 0:
sleep["average"] = sleep["total"] / sleep["count"]
if sleep["btwn_count"] > 0:
sleep["btwn_average"] = sleep["btwn_total"] / sleep["btwn_count"]
return sleep
@ -481,9 +521,9 @@ def _weight_statistics(child):
:param child: an instance of the Child model.
: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:
return False
@ -492,8 +532,8 @@ def _weight_statistics(child):
if newest != oldest:
weight_change = newest.weight - oldest.weight
weeks = (newest.date - oldest.date).days/7
weight['change_weekly'] = weight_change/weeks
weeks = (newest.date - oldest.date).days / 7
weight["change_weekly"] = weight_change / weeks
return weight
@ -504,9 +544,9 @@ def _height_statistics(child):
:param child: an instance of the Child model.
: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:
return False
@ -515,8 +555,8 @@ def _height_statistics(child):
if newest != oldest:
height_change = newest.height - oldest.height
weeks = (newest.date - oldest.date).days/7
height['change_weekly'] = height_change/weeks
weeks = (newest.date - oldest.date).days / 7
height["change_weekly"] = height_change / weeks
return height
@ -527,11 +567,9 @@ def _head_circumference_statistics(child):
:param child: an instance of the Child model.
:returns: a dictionary of statistics.
"""
head_circumference = {'change_weekly': 0.0}
head_circumference = {"change_weekly": 0.0}
instances = models.HeadCircumference.objects.filter(
child=child
).order_by('-date')
instances = models.HeadCircumference.objects.filter(child=child).order_by("-date")
if len(instances) == 0:
return False
@ -540,8 +578,8 @@ def _head_circumference_statistics(child):
if newest != oldest:
hc_change = newest.head_circumference - oldest.head_circumference
weeks = (newest.date - oldest.date).days/7
head_circumference['change_weekly'] = hc_change/weeks
weeks = (newest.date - oldest.date).days / 7
head_circumference["change_weekly"] = hc_change / weeks
return head_circumference
@ -552,9 +590,9 @@ def _bmi_statistics(child):
:param child: an instance of the Child model.
: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:
return False
@ -563,13 +601,13 @@ def _bmi_statistics(child):
if newest != oldest:
bmi_change = newest.bmi - oldest.bmi
weeks = (newest.date - oldest.date).days/7
bmi['change_weekly'] = bmi_change/weeks
weeks = (newest.date - oldest.date).days / 7
bmi["change_weekly"] = bmi_change / weeks
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):
"""
Filters for currently active Timer instances, optionally by child.
@ -579,42 +617,44 @@ def card_timer_list(context, child=None):
if child:
# Get active instances for the selected child _or_ None (no child).
instances = models.Timer.objects.filter(
Q(active=True),
Q(child=child) | Q(child=None)
).order_by('-start')
Q(active=True), Q(child=child) | Q(child=None)
).order_by("-start")
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
return {
'type': 'timer',
'instances': list(instances),
'empty': empty,
'hide_empty': _hide_empty(context)
"type": "timer",
"instances": list(instances),
"empty": empty,
"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):
"""
Filters the most recent tummy time.
:param child: an instance of the Child model.
:returns: a dictionary with the most recent Tummy Time instance.
"""
instance = models.TummyTime.objects.filter(child=child) \
.filter(**_filter_data_age(context)) \
.order_by('-end').first()
instance = (
models.TummyTime.objects.filter(child=child)
.filter(**_filter_data_age(context))
.order_by("-end")
.first()
)
empty = not instance
return {
'type': 'tummytime',
'tummytime': instance,
'empty': empty,
'hide_empty': _hide_empty(context)
"type": "tummytime",
"tummytime": instance,
"empty": empty,
"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):
"""
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:
date = timezone.localtime().date()
instances = models.TummyTime.objects.filter(
child=child, end__year=date.year, end__month=date.month,
end__day=date.day).order_by('-end')
child=child, end__year=date.year, end__month=date.month, end__day=date.day
).order_by("-end")
empty = len(instances) == 0
stats = {
'total': timezone.timedelta(seconds=0),
'count': instances.count()
}
stats = {"total": timezone.timedelta(seconds=0), "count": instances.count()}
for instance in instances:
stats['total'] += timezone.timedelta(seconds=instance.duration.seconds)
stats["total"] += timezone.timedelta(seconds=instance.duration.seconds)
return {
'type': 'tummytime',
'stats': stats,
'instances': instances,
'last': instances.first(),
'empty': empty,
'hide_empty': _hide_empty(context)
"type": "tummytime",
"stats": stats,
"instances": instances,
"last": instances.first(),
"empty": empty,
"hide_empty": _hide_empty(context),
}

View File

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

View File

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

View File

@ -3,13 +3,13 @@ from django.urls import path
from . import views
app_name = 'dashboard'
app_name = "dashboard"
urlpatterns = [
path('dashboard/', views.Dashboard.as_view(), name='dashboard'),
path("dashboard/", views.Dashboard.as_view(), name="dashboard"),
path(
'children/<str:slug>/dashboard/',
"children/<str:slug>/dashboard/",
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):
# 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.
def get(self, request, *args, **kwargs):
children = Child.objects.count()
if children == 0:
return HttpResponseRedirect(reverse('babybuddy:welcome'))
return HttpResponseRedirect(reverse("babybuddy:welcome"))
elif children == 1:
return HttpResponseRedirect(
reverse(
'dashboard:dashboard-child',
args={Child.objects.first().slug}
)
reverse("dashboard:dashboard-child", args={Child.objects.first().slug})
)
return super(Dashboard, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(Dashboard, self).get_context_data(**kwargs)
context['objects'] = Child.objects.all() \
.order_by('last_name', 'first_name', 'id')
context["objects"] = Child.objects.all().order_by(
"last_name", "first_name", "id"
)
return context
class ChildDashboard(PermissionRequiredMixin, DetailView):
model = Child
permission_required = ('core.view_child',)
template_name = 'dashboard/child.html'
permission_required = ("core.view_child",)
template_name = "dashboard/child.html"

View File

@ -1,5 +1,5 @@
# Server mechanics
bind = '0.0.0.0:8000'
bind = "0.0.0.0:8000"
backlog = 2048
daemon = False
pidfile = None
@ -10,9 +10,9 @@ tmp_upload_dir = None
proc_name = None
# Logging
errorlog = '-'
loglevel = 'info'
accesslog = '-'
errorlog = "-"
loglevel = "info"
accesslog = "-"
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
worker_class = 'sync'
worker_class = "sync"
worker_connections = 1000
timeout = 30
keepalive = 2
@ -112,14 +112,13 @@ def worker_int(worker):
# get traceback info
import threading, sys, traceback
id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
code = []
for threadId, stack in sys._current_frames().items():
code.append("\n# Thread: %s(%d)" % (id2name.get(threadId,""),
threadId))
code.append("\n# Thread: %s(%d)" % (id2name.get(threadId, ""), threadId))
for filename, lineno, name, line in traceback.extract_stack(stack):
code.append('File: "%s", line %d, in %s' % (filename,
lineno, name))
code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
if line:
code.append(" %s" % (line.strip()))
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 .weight_weight import weight_weight # 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

View File

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

View File

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

View File

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

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