mirror of https://github.com/snachodog/mybuddy.git
Add weight tracking.
This commit is contained in:
parent
f947d37285
commit
067be4bf07
|
@ -5,8 +5,7 @@ from rest_framework import serializers
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
from core.models import (Child, DiaperChange, Feeding, Note, Sleep, Timer,
|
from core import models
|
||||||
TummyTime)
|
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
|
@ -17,7 +16,7 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class ChildSerializer(serializers.HyperlinkedModelSerializer):
|
class ChildSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Child
|
model = models.Child
|
||||||
fields = ('first_name', 'last_name', 'birth_date', 'slug')
|
fields = ('first_name', 'last_name', 'birth_date', 'slug')
|
||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
|
|
||||||
|
@ -26,7 +25,7 @@ class DiaperChangeSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
child = ChildSerializer()
|
child = ChildSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DiaperChange
|
model = models.DiaperChange
|
||||||
fields = ('child', 'time', 'wet', 'solid', 'color')
|
fields = ('child', 'time', 'wet', 'solid', 'color')
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,7 +33,7 @@ class FeedingSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
child = ChildSerializer()
|
child = ChildSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Feeding
|
model = models.Feeding
|
||||||
fields = ('child', 'start', 'end', 'duration', 'type', 'method',
|
fields = ('child', 'start', 'end', 'duration', 'type', 'method',
|
||||||
'amount')
|
'amount')
|
||||||
|
|
||||||
|
@ -43,7 +42,7 @@ class NoteSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
child = ChildSerializer()
|
child = ChildSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Note
|
model = models.Note
|
||||||
fields = ('child', 'note', 'time')
|
fields = ('child', 'note', 'time')
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,7 +50,7 @@ class SleepSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
child = ChildSerializer()
|
child = ChildSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Sleep
|
model = models.Sleep
|
||||||
fields = ('child', 'start', 'end', 'duration')
|
fields = ('child', 'start', 'end', 'duration')
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,7 +58,7 @@ class TimerSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
user = UserSerializer()
|
user = UserSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Timer
|
model = models.Timer
|
||||||
fields = ('name', 'start', 'end', 'duration', 'active', 'user')
|
fields = ('name', 'start', 'end', 'duration', 'active', 'user')
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,5 +66,13 @@ class TummyTimeSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
child = ChildSerializer()
|
child = ChildSerializer()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TummyTime
|
model = models.TummyTime
|
||||||
fields = ('child', 'start', 'end', 'duration', 'milestone')
|
fields = ('child', 'start', 'end', 'duration', 'milestone')
|
||||||
|
|
||||||
|
|
||||||
|
class WeightSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
child = ChildSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Weight
|
||||||
|
fields = ('child', 'weight', 'date')
|
||||||
|
|
19
api/urls.py
19
api/urls.py
|
@ -4,17 +4,18 @@ from __future__ import unicode_literals
|
||||||
from django.conf.urls import url, include
|
from django.conf.urls import url, include
|
||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
|
|
||||||
from .views import (ChildViewSet, DiaperChangeViewSet, FeedingViewSet,
|
from . import views
|
||||||
NoteViewSet, SleepViewSet, TimerViewSet, TummyTimeViewSet)
|
|
||||||
|
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.register(r'children', ChildViewSet)
|
router.register(r'children', views.ChildViewSet)
|
||||||
router.register(r'changes', DiaperChangeViewSet)
|
router.register(r'changes', views.DiaperChangeViewSet)
|
||||||
router.register(r'feedings', FeedingViewSet)
|
router.register(r'feedings', views.FeedingViewSet)
|
||||||
router.register(r'notes', NoteViewSet)
|
router.register(r'notes', views.NoteViewSet)
|
||||||
router.register(r'sleep', SleepViewSet)
|
router.register(r'sleep', views.SleepViewSet)
|
||||||
router.register(r'timers', TimerViewSet)
|
router.register(r'timers', views.TimerViewSet)
|
||||||
router.register(r'tummy-times', TummyTimeViewSet)
|
router.register(r'tummy-times', views.TummyTimeViewSet)
|
||||||
|
router.register(r'weight', views.WeightViewSet)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^api/', include(router.urls)),
|
url(r'^api/', include(router.urls)),
|
||||||
|
|
41
api/views.py
41
api/views.py
|
@ -3,52 +3,55 @@ from __future__ import unicode_literals
|
||||||
|
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
|
|
||||||
from core.models import (Child, DiaperChange, Feeding, Note, Sleep, Timer,
|
from core import models
|
||||||
TummyTime)
|
|
||||||
|
|
||||||
from .serializers import (ChildSerializer, DiaperChangeSerializer,
|
from . import serializers
|
||||||
FeedingSerializer, NoteSerializer, SleepSerializer,
|
|
||||||
TimerSerializer, TummyTimeSerializer,)
|
|
||||||
|
|
||||||
|
|
||||||
class ChildViewSet(viewsets.ModelViewSet):
|
class ChildViewSet(viewsets.ModelViewSet):
|
||||||
queryset = Child.objects.all()
|
queryset = models.Child.objects.all()
|
||||||
serializer_class = ChildSerializer
|
serializer_class = serializers.ChildSerializer
|
||||||
lookup_field = 'slug'
|
lookup_field = 'slug'
|
||||||
filter_fields = ('first_name', 'last_name', 'slug')
|
filter_fields = ('first_name', 'last_name', 'slug')
|
||||||
|
|
||||||
|
|
||||||
class DiaperChangeViewSet(viewsets.ModelViewSet):
|
class DiaperChangeViewSet(viewsets.ModelViewSet):
|
||||||
queryset = DiaperChange.objects.all()
|
queryset = models.DiaperChange.objects.all()
|
||||||
serializer_class = DiaperChangeSerializer
|
serializer_class = serializers.DiaperChangeSerializer
|
||||||
filter_fields = ('child', 'wet', 'solid', 'color')
|
filter_fields = ('child', 'wet', 'solid', 'color')
|
||||||
|
|
||||||
|
|
||||||
class FeedingViewSet(viewsets.ModelViewSet):
|
class FeedingViewSet(viewsets.ModelViewSet):
|
||||||
queryset = Feeding.objects.all()
|
queryset = models.Feeding.objects.all()
|
||||||
serializer_class = FeedingSerializer
|
serializer_class = serializers.FeedingSerializer
|
||||||
filter_fields = ('child', 'type', 'method')
|
filter_fields = ('child', 'type', 'method')
|
||||||
|
|
||||||
|
|
||||||
class NoteViewSet(viewsets.ModelViewSet):
|
class NoteViewSet(viewsets.ModelViewSet):
|
||||||
queryset = Note.objects.all()
|
queryset = models.Note.objects.all()
|
||||||
serializer_class = NoteSerializer
|
serializer_class = serializers.NoteSerializer
|
||||||
filter_fields = ('child',)
|
filter_fields = ('child',)
|
||||||
|
|
||||||
|
|
||||||
class SleepViewSet(viewsets.ModelViewSet):
|
class SleepViewSet(viewsets.ModelViewSet):
|
||||||
queryset = Sleep.objects.all()
|
queryset = models.Sleep.objects.all()
|
||||||
serializer_class = SleepSerializer
|
serializer_class = serializers.SleepSerializer
|
||||||
filter_fields = ('child',)
|
filter_fields = ('child',)
|
||||||
|
|
||||||
|
|
||||||
class TimerViewSet(viewsets.ModelViewSet):
|
class TimerViewSet(viewsets.ModelViewSet):
|
||||||
queryset = Timer.objects.all()
|
queryset = models.Timer.objects.all()
|
||||||
serializer_class = TimerSerializer
|
serializer_class = serializers.TimerSerializer
|
||||||
filter_fields = ('active', 'user')
|
filter_fields = ('active', 'user')
|
||||||
|
|
||||||
|
|
||||||
class TummyTimeViewSet(viewsets.ModelViewSet):
|
class TummyTimeViewSet(viewsets.ModelViewSet):
|
||||||
queryset = TummyTime.objects.all()
|
queryset = models.TummyTime.objects.all()
|
||||||
serializer_class = TummyTimeSerializer
|
serializer_class = serializers.TummyTimeSerializer
|
||||||
|
filter_fields = ('child',)
|
||||||
|
|
||||||
|
|
||||||
|
class WeightViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = models.Weight.objects.all()
|
||||||
|
serializer_class = serializers.WeightSerializer
|
||||||
filter_fields = ('child',)
|
filter_fields = ('child',)
|
||||||
|
|
|
@ -10,7 +10,7 @@ from django.utils import timezone
|
||||||
|
|
||||||
from faker import Factory
|
from faker import Factory
|
||||||
|
|
||||||
from core.models import Child, DiaperChange, Feeding, Note, Sleep, TummyTime
|
from core import models
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
@ -42,7 +42,7 @@ class Command(BaseCommand):
|
||||||
# User first day of data that will created for birth date.
|
# User first day of data that will created for birth date.
|
||||||
birth_date = (timezone.localtime() - timedelta(days=days))
|
birth_date = (timezone.localtime() - timedelta(days=days))
|
||||||
for i in range(0, children):
|
for i in range(0, children):
|
||||||
child = Child.objects.create(
|
child = models.Child.objects.create(
|
||||||
first_name=self.faker.first_name(),
|
first_name=self.faker.first_name(),
|
||||||
last_name=self.faker.last_name(),
|
last_name=self.faker.last_name(),
|
||||||
birth_date=birth_date
|
birth_date=birth_date
|
||||||
|
@ -67,7 +67,7 @@ class Command(BaseCommand):
|
||||||
if solid:
|
if solid:
|
||||||
wet = False
|
wet = False
|
||||||
color = choice(
|
color = choice(
|
||||||
DiaperChange._meta.get_field('color').choices)[0]
|
models.DiaperChange._meta.get_field('color').choices)[0]
|
||||||
else:
|
else:
|
||||||
wet = True
|
wet = True
|
||||||
color = ''
|
color = ''
|
||||||
|
@ -75,7 +75,7 @@ class Command(BaseCommand):
|
||||||
time = date + timedelta(minutes=randint(0, 60 * 24))
|
time = date + timedelta(minutes=randint(0, 60 * 24))
|
||||||
|
|
||||||
if time < now:
|
if time < now:
|
||||||
DiaperChange.objects.create(
|
models.DiaperChange.objects.create(
|
||||||
child=child,
|
child=child,
|
||||||
time=time,
|
time=time,
|
||||||
wet=wet,
|
wet=wet,
|
||||||
|
@ -85,7 +85,8 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
last_end = date
|
last_end = date
|
||||||
while last_end < date + timedelta(days=1):
|
while last_end < date + timedelta(days=1):
|
||||||
method = choice(Feeding._meta.get_field('method').choices)[0]
|
method = choice(models.Feeding._meta.get_field(
|
||||||
|
'method').choices)[0]
|
||||||
if method is 'bottle':
|
if method is 'bottle':
|
||||||
amount = Decimal('%d.%d' % (randint(0, 6), randint(0, 9)))
|
amount = Decimal('%d.%d' % (randint(0, 6), randint(0, 9)))
|
||||||
else:
|
else:
|
||||||
|
@ -96,11 +97,11 @@ class Command(BaseCommand):
|
||||||
if end > now:
|
if end > now:
|
||||||
break
|
break
|
||||||
|
|
||||||
Feeding.objects.create(
|
models.Feeding.objects.create(
|
||||||
child=child,
|
child=child,
|
||||||
start=start,
|
start=start,
|
||||||
end=end,
|
end=end,
|
||||||
type=choice(Feeding._meta.get_field('type').choices)[0],
|
type=choice(models.Feeding._meta.get_field('type').choices)[0],
|
||||||
method=method,
|
method=method,
|
||||||
amount=amount
|
amount=amount
|
||||||
).save()
|
).save()
|
||||||
|
@ -109,7 +110,8 @@ class Command(BaseCommand):
|
||||||
last_end = date
|
last_end = date
|
||||||
|
|
||||||
# Adjust last_end if the last sleep entry crossed in to date.
|
# Adjust last_end if the last sleep entry crossed in to date.
|
||||||
last_entry = Sleep.objects.filter(child=child).order_by('end').last()
|
last_entry = models.Sleep.objects.filter(
|
||||||
|
child=child).order_by('end').last()
|
||||||
if last_entry:
|
if last_entry:
|
||||||
last_entry_end = timezone.localtime(last_entry.end)
|
last_entry_end = timezone.localtime(last_entry.end)
|
||||||
if last_entry_end > last_end:
|
if last_entry_end > last_end:
|
||||||
|
@ -124,7 +126,8 @@ class Command(BaseCommand):
|
||||||
if end > now:
|
if end > now:
|
||||||
break
|
break
|
||||||
|
|
||||||
Sleep.objects.create(child=child, start=start, end=end).save()
|
models.Sleep.objects.create(
|
||||||
|
child=child, start=start, end=end).save()
|
||||||
last_end = end
|
last_end = end
|
||||||
|
|
||||||
last_end = date
|
last_end = date
|
||||||
|
@ -139,7 +142,7 @@ class Command(BaseCommand):
|
||||||
if end > now:
|
if end > now:
|
||||||
break
|
break
|
||||||
|
|
||||||
TummyTime.objects.create(
|
models.TummyTime.objects.create(
|
||||||
child=child,
|
child=child,
|
||||||
start=start,
|
start=start,
|
||||||
end=end,
|
end=end,
|
||||||
|
@ -147,8 +150,14 @@ class Command(BaseCommand):
|
||||||
).save()
|
).save()
|
||||||
last_end = end
|
last_end = end
|
||||||
|
|
||||||
|
models.Weight.objects.create(
|
||||||
|
child=child,
|
||||||
|
weight=Decimal('%d.%d' % (randint(3, 15), randint(0, 9))),
|
||||||
|
date=date.date()
|
||||||
|
).save()
|
||||||
|
|
||||||
note = self.faker.sentence()
|
note = self.faker.sentence()
|
||||||
Note.objects.create(
|
models.Note.objects.create(
|
||||||
child=child,
|
child=child,
|
||||||
note=note,
|
note=note,
|
||||||
time=date + timedelta(minutes=randint(0, 60 * 24))
|
time=date + timedelta(minutes=randint(0, 60 * 24))
|
||||||
|
|
|
@ -103,3 +103,7 @@
|
||||||
.icon-user {
|
.icon-user {
|
||||||
@extend .fa-user;
|
@extend .fa-user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-weight {
|
||||||
|
@extend .fa-balance-scale;
|
||||||
|
}
|
||||||
|
|
|
@ -43,6 +43,11 @@
|
||||||
<i class="icon icon-tummytime" aria-hidden="true"></i> Tummy Time
|
<i class="icon icon-tummytime" aria-hidden="true"></i> Tummy Time
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if perms.core.add_weight %}
|
||||||
|
<a class="dropdown-item p-2" href="{% url 'weight-add' %}">
|
||||||
|
<i class="icon icon-weight" aria-hidden="true"></i> Weight
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -91,6 +96,16 @@
|
||||||
href="{% url 'note-add' %}"><i class="icon icon-add" aria-hidden="true"></i> Note</a>
|
href="{% url 'note-add' %}"><i class="icon icon-add" aria-hidden="true"></i> Note</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if perms.core.view_weight %}
|
||||||
|
<a class="dropdown-item{% if request.path == '/weight/' %} active{% endif %}" href="{% url 'weight-list' %}">
|
||||||
|
<i class="icon icon-weight" aria-hidden="true"></i> Weight
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.core.add_weight %}
|
||||||
|
<a class="dropdown-item pl-5{% if request.path == '/weight/add/' %} active{% endif %}"
|
||||||
|
href="{% url 'weight-add' %}"><i class="icon icon-add" aria-hidden="true"></i> Weight entry</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
|
@ -3,25 +3,24 @@ from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import (Child, DiaperChange, Feeding, Note, Sleep, Timer,
|
from core import models
|
||||||
TummyTime)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Child)
|
@admin.register(models.Child)
|
||||||
class ChildAdmin(admin.ModelAdmin):
|
class ChildAdmin(admin.ModelAdmin):
|
||||||
list_display = ('first_name', 'last_name', 'birth_date', 'slug')
|
list_display = ('first_name', 'last_name', 'birth_date', 'slug')
|
||||||
list_filter = ('last_name',)
|
list_filter = ('last_name',)
|
||||||
search_fields = ('first_name', 'last_name', 'birth_date',)
|
search_fields = ('first_name', 'last_name', 'birth_date',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(DiaperChange)
|
@admin.register(models.DiaperChange)
|
||||||
class DiaperChangeAdmin(admin.ModelAdmin):
|
class DiaperChangeAdmin(admin.ModelAdmin):
|
||||||
list_display = ('child', 'time', 'wet', 'solid', 'color')
|
list_display = ('child', 'time', 'wet', 'solid', 'color')
|
||||||
list_filter = ('child', 'wet', 'solid', 'color')
|
list_filter = ('child', 'wet', 'solid', 'color')
|
||||||
search_fields = ('child__first_name', 'child__last_name',)
|
search_fields = ('child__first_name', 'child__last_name',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Feeding)
|
@admin.register(models.Feeding)
|
||||||
class FeedingAdmin(admin.ModelAdmin):
|
class FeedingAdmin(admin.ModelAdmin):
|
||||||
list_display = ('start', 'end', 'duration', 'child', 'type', 'method',
|
list_display = ('start', 'end', 'duration', 'child', 'type', 'method',
|
||||||
'amount')
|
'amount')
|
||||||
|
@ -30,29 +29,36 @@ class FeedingAdmin(admin.ModelAdmin):
|
||||||
'method',)
|
'method',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Note)
|
@admin.register(models.Note)
|
||||||
class NoteAdmin(admin.ModelAdmin):
|
class NoteAdmin(admin.ModelAdmin):
|
||||||
list_display = ('time', 'child', 'note',)
|
list_display = ('time', 'child', 'note',)
|
||||||
list_filter = ('child',)
|
list_filter = ('child',)
|
||||||
search_fields = ('child__last_name',)
|
search_fields = ('child__last_name',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Sleep)
|
@admin.register(models.Sleep)
|
||||||
class SleepAdmin(admin.ModelAdmin):
|
class SleepAdmin(admin.ModelAdmin):
|
||||||
list_display = ('start', 'end', 'duration', 'child', 'nap')
|
list_display = ('start', 'end', 'duration', 'child', 'nap')
|
||||||
list_filter = ('child',)
|
list_filter = ('child',)
|
||||||
search_fields = ('child__first_name', 'child__last_name',)
|
search_fields = ('child__first_name', 'child__last_name',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Timer)
|
@admin.register(models.Timer)
|
||||||
class TimerAdmin(admin.ModelAdmin):
|
class TimerAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'start', 'end', 'duration', 'active', 'user')
|
list_display = ('name', 'start', 'end', 'duration', 'active', 'user')
|
||||||
list_filter = ('active', 'user')
|
list_filter = ('active', 'user')
|
||||||
search_fields = ('name', 'user')
|
search_fields = ('name', 'user')
|
||||||
|
|
||||||
|
|
||||||
@admin.register(TummyTime)
|
@admin.register(models.TummyTime)
|
||||||
class TummyTimeAdmin(admin.ModelAdmin):
|
class TummyTimeAdmin(admin.ModelAdmin):
|
||||||
list_display = ('start', 'end', 'duration', 'child', 'milestone',)
|
list_display = ('start', 'end', 'duration', 'child', 'milestone',)
|
||||||
list_filter = ('child',)
|
list_filter = ('child',)
|
||||||
search_fields = ('child__first_name', 'child__last_name', 'milestone',)
|
search_fields = ('child__first_name', 'child__last_name', 'milestone',)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.Weight)
|
||||||
|
class WeightAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('child', 'weight', 'date',)
|
||||||
|
list_filter = ('child',)
|
||||||
|
search_fields = ('child__first_name', 'child__last_name', 'weight',)
|
||||||
|
|
|
@ -205,3 +205,19 @@ class TummyTimeForm(forms.ModelForm):
|
||||||
timer.stop(instance.end)
|
timer.stop(instance.end)
|
||||||
instance.save()
|
instance.save()
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class WeightForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = models.Weight
|
||||||
|
fields = ['child', 'weight', 'date']
|
||||||
|
widgets = {
|
||||||
|
'date': forms.DateInput(attrs={
|
||||||
|
'class': 'datepicker-input',
|
||||||
|
'data-target': '#datetimepicker_date',
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
kwargs = set_default_child(kwargs)
|
||||||
|
super(WeightForm, self).__init__(*args, **kwargs)
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.6 on 2017-11-10 02:07
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0002_auto_20171028_1257'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
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')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'default_permissions': ('view', 'add', 'change', 'delete'),
|
||||||
|
'verbose_name_plural': 'Weight',
|
||||||
|
'ordering': ['-date'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -10,6 +10,19 @@ from django.template.defaultfilters import slugify
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
|
def validate_date(date, field_name):
|
||||||
|
"""
|
||||||
|
Confirm that a date is not in the future.
|
||||||
|
:param date: a timezone aware date instance.
|
||||||
|
:param field_name: the name of the field being checked.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if date and date > timezone.localdate():
|
||||||
|
raise ValidationError(
|
||||||
|
{field_name: 'Date can not be in the future.'},
|
||||||
|
code='date_invalid')
|
||||||
|
|
||||||
|
|
||||||
def validate_duration(model, max_duration=timedelta(hours=24)):
|
def validate_duration(model, max_duration=timedelta(hours=24)):
|
||||||
"""
|
"""
|
||||||
Basic sanity checks for models with a duration
|
Basic sanity checks for models with a duration
|
||||||
|
@ -325,3 +338,23 @@ class TummyTime(models.Model):
|
||||||
validate_duration(self)
|
validate_duration(self)
|
||||||
validate_unique_period(
|
validate_unique_period(
|
||||||
TummyTime.objects.filter(child=self.child), self)
|
TummyTime.objects.filter(child=self.child), self)
|
||||||
|
|
||||||
|
|
||||||
|
class Weight(models.Model):
|
||||||
|
model_name = 'weight'
|
||||||
|
child = models.ForeignKey('Child', related_name='weight')
|
||||||
|
weight = models.FloatField(blank=False, null=False)
|
||||||
|
date = models.DateField(blank=False, null=False)
|
||||||
|
|
||||||
|
objects = models.Manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
default_permissions = ('view', 'add', 'change', 'delete')
|
||||||
|
ordering = ['-date']
|
||||||
|
verbose_name_plural = 'Weight'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return 'Weight'
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
validate_date(self.date, 'date')
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
{% extends 'babybuddy/page.html' %}
|
||||||
|
{% load widget_tweaks %}
|
||||||
|
|
||||||
|
{% block title %}Delete a Weight Entry{% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<li class="breadcrumb-item"><a href="{% url 'weight-list' %}">Weight</a></li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">Delete</li>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form role="form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<h1>Are you sure you want to delete <span class="text-info">{{ object }}</span>?</h1>
|
||||||
|
<input type="submit" value="Delete" class="btn btn-danger" />
|
||||||
|
<a href="{% url 'weight-list' %}" class="btn btn-default">Cancel</a>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,38 @@
|
||||||
|
{% extends 'babybuddy/page.html' %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% if object %}
|
||||||
|
{{ object }}
|
||||||
|
{% else %}
|
||||||
|
Add a Weight Entry
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<li class="breadcrumb-item"><a href="{% url 'weight-list' %}">Weight</a></li>
|
||||||
|
{% if object %}
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">Update</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">Add a Weight Entry</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% if object %}
|
||||||
|
<h1>Update <span class="text-info">{{ object }}</span></h1>
|
||||||
|
{% else %}
|
||||||
|
<h1>Add a Weight Entry</h1>
|
||||||
|
{% endif %}
|
||||||
|
{% include 'babybuddy/form.html' %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javascript %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function () {
|
||||||
|
$('#datetimepicker_date').datetimepicker({
|
||||||
|
defaultDate: 'now',
|
||||||
|
format: 'YYYY-MM-DD'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,63 @@
|
||||||
|
{% extends 'babybuddy/page.html' %}
|
||||||
|
{% load widget_tweaks %}
|
||||||
|
|
||||||
|
{% block title %}Weight{% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">Weight</li>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Weight</h1>
|
||||||
|
{% include 'babybuddy/filter.html' %}
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead class="thead-inverse">
|
||||||
|
<tr>
|
||||||
|
<th>Child</th>
|
||||||
|
<th>Weight</th>
|
||||||
|
<th>Date</th>
|
||||||
|
<th class="text-center">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for object in object_list %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><a href="{% url 'child' object.child.slug %}">{{ object.child }}</a></th>
|
||||||
|
<td>{{ object.weight }}</td>
|
||||||
|
<td>{{ object.date }}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<div class="btn-group btn-group-sm" role="group" aria-label="Actions">
|
||||||
|
|
||||||
|
{% if perms.core.change_weight %}
|
||||||
|
<a href="{% url 'weight-update' object.id %}" class="btn btn-primary">
|
||||||
|
<i class="icon icon-update" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if perms.core.delete_weight %}
|
||||||
|
<a href="{% url 'weight-delete' object.id %}" class="btn btn-danger">
|
||||||
|
<i class="icon icon-delete" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<th colspan="4">No weight entries found.</th>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% include 'babybuddy/paginator.html' %}
|
||||||
|
|
||||||
|
{% if perms.core.add_weight %}
|
||||||
|
<a href="{% url 'weight-add' %}" class="btn btn-sm btn-success">
|
||||||
|
<i class="icon icon-weight" aria-hidden="true"></i> Add a Weight Entry
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -180,6 +180,31 @@ class FormsTestCase(TestCase):
|
||||||
page = self.c.post('/tummy-time/{}/'.format(entry.id), params)
|
page = self.c.post('/tummy-time/{}/'.format(entry.id), params)
|
||||||
self.assertEqual(page.status_code, 302)
|
self.assertEqual(page.status_code, 302)
|
||||||
|
|
||||||
|
def test_weight_forms(self):
|
||||||
|
params = {
|
||||||
|
'child': 1,
|
||||||
|
'weight': '8.5',
|
||||||
|
'date': '2000-01-01'
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = models.Weight.objects.first()
|
||||||
|
page = self.c.post('/weight/{}/'.format(entry.id), params)
|
||||||
|
self.assertEqual(page.status_code, 302)
|
||||||
|
|
||||||
|
def test_validate_date(self):
|
||||||
|
future = (timezone.localdate() + timezone.timedelta(days=1))
|
||||||
|
params = {
|
||||||
|
'child': 1,
|
||||||
|
'weight': '8.5',
|
||||||
|
'date': future.strftime('%Y-%m-%d')
|
||||||
|
}
|
||||||
|
entry = models.Weight.objects.first()
|
||||||
|
|
||||||
|
page = self.c.post('/weight/{}/'.format(entry.id), params)
|
||||||
|
self.assertEqual(page.status_code, 200)
|
||||||
|
self.assertFormError(page, 'form', 'date',
|
||||||
|
'Date can not be in the future.')
|
||||||
|
|
||||||
def test_validate_duration(self):
|
def test_validate_duration(self):
|
||||||
params = {
|
params = {
|
||||||
'child': 1,
|
'child': 1,
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.test import TestCase
|
|
||||||
from django.test import Client as HttpClient
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.test import Client as HttpClient
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from faker import Factory
|
from faker import Factory
|
||||||
|
|
||||||
|
@ -40,6 +41,11 @@ class ViewsTestCase(TestCase):
|
||||||
entry = models.Child.objects.first()
|
entry = models.Child.objects.first()
|
||||||
page = self.c.get('/children/{}/'.format(entry.slug))
|
page = self.c.get('/children/{}/'.format(entry.slug))
|
||||||
self.assertEqual(page.status_code, 200)
|
self.assertEqual(page.status_code, 200)
|
||||||
|
page = self.c.get(
|
||||||
|
'/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)
|
self.assertEqual(page.status_code, 200)
|
||||||
page = self.c.get('/children/{}/delete/'.format(entry.slug))
|
page = self.c.get('/children/{}/delete/'.format(entry.slug))
|
||||||
|
@ -124,3 +130,15 @@ class ViewsTestCase(TestCase):
|
||||||
self.assertEqual(page.status_code, 200)
|
self.assertEqual(page.status_code, 200)
|
||||||
page = self.c.get('/tummy-time/{}/delete/'.format(entry.id))
|
page = self.c.get('/tummy-time/{}/delete/'.format(entry.id))
|
||||||
self.assertEqual(page.status_code, 200)
|
self.assertEqual(page.status_code, 200)
|
||||||
|
|
||||||
|
def test_weight_views(self):
|
||||||
|
page = self.c.get('/weight/')
|
||||||
|
self.assertEqual(page.status_code, 200)
|
||||||
|
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))
|
||||||
|
self.assertEqual(page.status_code, 200)
|
||||||
|
page = self.c.get('/weight/{}/delete/'.format(entry.id))
|
||||||
|
self.assertEqual(page.status_code, 200)
|
||||||
|
|
|
@ -68,4 +68,11 @@ urlpatterns = [
|
||||||
name='tummytime-update'),
|
name='tummytime-update'),
|
||||||
url(r'^tummy-time/(?P<pk>[0-9]+)/delete/$',
|
url(r'^tummy-time/(?P<pk>[0-9]+)/delete/$',
|
||||||
views.TummyTimeDelete.as_view(), name='tummytime-delete'),
|
views.TummyTimeDelete.as_view(), name='tummytime-delete'),
|
||||||
|
|
||||||
|
url(r'^weight/$', views.WeightList.as_view(), name='weight-list'),
|
||||||
|
url(r'^weight/add/$', views.WeightAdd.as_view(), name='weight-add'),
|
||||||
|
url(r'^weight/(?P<pk>[0-9]+)/$', views.WeightUpdate.as_view(),
|
||||||
|
name='weight-update'),
|
||||||
|
url(r'^weight/(?P<pk>[0-9]+)/delete/$', views.WeightDelete.as_view(),
|
||||||
|
name='weight-delete'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -298,3 +298,31 @@ class TummyTimeDelete(PermissionRequiredMixin, DeleteView):
|
||||||
model = models.TummyTime
|
model = models.TummyTime
|
||||||
permission_required = ('core.delete_tummytime',)
|
permission_required = ('core.delete_tummytime',)
|
||||||
success_url = '/tummy-time'
|
success_url = '/tummy-time'
|
||||||
|
|
||||||
|
|
||||||
|
class WeightList(PermissionRequiredMixin, FilterView):
|
||||||
|
model = models.Weight
|
||||||
|
template_name = 'core/weight_list.html'
|
||||||
|
permission_required = ('core.view_weight',)
|
||||||
|
paginate_by = 10
|
||||||
|
filter_fields = ('child',)
|
||||||
|
|
||||||
|
|
||||||
|
class WeightAdd(PermissionRequiredMixin, CreateView):
|
||||||
|
model = models.Weight
|
||||||
|
permission_required = ('core.add_weight',)
|
||||||
|
form_class = forms.WeightForm
|
||||||
|
success_url = '/weight'
|
||||||
|
|
||||||
|
|
||||||
|
class WeightUpdate(PermissionRequiredMixin, UpdateView):
|
||||||
|
model = models.Weight
|
||||||
|
permission_required = ('core.change_weight',)
|
||||||
|
fields = ['child', 'weight', 'date']
|
||||||
|
success_url = '/weight'
|
||||||
|
|
||||||
|
|
||||||
|
class WeightDelete(PermissionRequiredMixin, DeleteView):
|
||||||
|
model = models.Weight
|
||||||
|
permission_required = ('core.delete_weight',)
|
||||||
|
success_url = '/weight'
|
||||||
|
|
Loading…
Reference in New Issue