Move timeline code to core and integrate with Child detail view.

This commit is contained in:
Christopher Charbonneau Wells 2017-11-07 13:15:48 -05:00
parent 1c5278cebf
commit 162f117cef
11 changed files with 121 additions and 132 deletions

View File

@ -3,3 +3,12 @@
max-width: 150px;
}
}
@include media-breakpoint-up(md) {
#view-child {
.child-detail-column {
position: fixed;
width: 100%;
}
}
}

View File

@ -118,7 +118,7 @@ $card-shadow: rgba(0, 0, 0, .175);
}
}
@media (max-width: 767px) {
@include media-breakpoint-down(md) {
.timeline {
&::before {
left: 40px;

View File

@ -9,7 +9,9 @@
{% endblock %}
{% block content %}
<div class="jumbotron text-center">
<div class="child-detail-column">
<div class="row">
<div class="col-lg-4 col-md-6 pb-3 text-center">
<img src="{% static 'babybuddy/img/core/child-placeholder.png' %}" class="child-photo img-fluid rounded-circle" />
<div class="child-name display-4">{{ object }}</div>
<p class="lead">
@ -18,4 +20,42 @@
</p>
{% include 'dashboard/child_button_group.html' %}
</div>
</div>
</div>
<div class="row">
<div class="col-lg-8 offset-lg-4 col-md-6 offset-md-6">
<h3 class="text-center">
{% if date_previous %}
<a class="btn btn-sm btn-default" href="?date={{ date_previous|date:"Y-m-d" }}" aria-label="Previous">
<i class="icon icon-chevron-left" aria-hidden="true"></i>
<span class="sr-only">Previous</span>
</a>
{% endif %}
{{ date|date }}
{% if date_next %}
<a class="btn btn-sm btn-default" href="?date={{ date_next|date:"Y-m-d" }}" aria-label="Next">
<i class="icon icon-chevron-right" aria-hidden="true"></i>
<span class="sr-only">Next</span>
</a>
{% endif %}
</h3>
<ul class="timeline m-auto">
{% for object in timeline_objects %}
<li{% cycle "" ' class="timeline-inverted"' %}>
<div class="timeline-badge {% if object.type == "start" %}bg-success{% elif object.type == "end" %}bg-danger{% else %}bg-info{% endif %}">
<i class="icon icon-{{ object.model_name }}"></i>
</div>
<div class="card text-right">
<div class="card-body">
{{ object.event }}
</div>
<div class="card-footer text-muted">
{{ object.time|timesince }} ago ({{ object.time|time }})
</div>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endblock %}

View File

@ -6,9 +6,9 @@ from django.utils import timezone
from core.models import DiaperChange, Feeding, Sleep, TummyTime
def timeline(child, date):
def get_objects(child, date):
"""
Create a time-sorted dictionary for all events for a child.
Create a time-sorted dictionary of all events for a child.
:param child: an instance of a Child.
:param date: a DateTime instance for the day to be summarized.
:returns: a list of the day's events.

View File

@ -3,19 +3,18 @@ from __future__ import unicode_literals
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.urls import reverse
from django.utils import timezone
from django.views.generic.base import RedirectView
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django_filters.views import FilterView
from .models import Child, DiaperChange, Feeding, Note, Sleep, Timer, TummyTime
from .forms import (ChildForm, ChildDeleteForm, DiaperChangeForm, FeedingForm,
SleepForm, TimerForm, TummyTimeForm)
from core import forms, models, timeline
class ChildList(PermissionRequiredMixin, FilterView):
model = Child
model = models.Child
template_name = 'core/child_list.html'
permission_required = ('core.view_child',)
paginate_by = 10
@ -23,34 +22,46 @@ class ChildList(PermissionRequiredMixin, FilterView):
class ChildAdd(PermissionRequiredMixin, CreateView):
model = Child
model = models.Child
permission_required = ('core.add_child',)
form_class = ChildForm
form_class = forms.ChildForm
success_url = '/children'
class ChildDetail(PermissionRequiredMixin, DetailView):
model = Child
model = models.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 = timezone.datetime.strptime(date, '%Y-%m-%d')
date = timezone.localtime(timezone.make_aware(date))
context['timeline_objects'] = timeline.get_objects(self.object, date)
context['date'] = date
context['date_previous'] = date - timezone.timedelta(days=1)
if date.date() < timezone.localdate():
context['date_next'] = date + timezone.timedelta(days=1)
return context
class ChildUpdate(PermissionRequiredMixin, UpdateView):
model = Child
model = models.Child
permission_required = ('core.change_child',)
form_class = ChildForm
form_class = forms.ChildForm
success_url = '/children'
class ChildDelete(PermissionRequiredMixin, UpdateView):
model = Child
form_class = ChildDeleteForm
model = models.Child
form_class = forms.ChildDeleteForm
template_name = 'core/child_confirm_delete.html'
permission_required = ('core.delete_child',)
success_url = '/children'
class DiaperChangeList(PermissionRequiredMixin, FilterView):
model = DiaperChange
model = models.DiaperChange
template_name = 'core/diaperchange_list.html'
permission_required = ('core.view_diaperchange',)
paginate_by = 10
@ -58,27 +69,27 @@ class DiaperChangeList(PermissionRequiredMixin, FilterView):
class DiaperChangeAdd(PermissionRequiredMixin, CreateView):
model = DiaperChange
model = models.DiaperChange
permission_required = ('core.add_diaperchange',)
form_class = DiaperChangeForm
form_class = forms.DiaperChangeForm
success_url = '/changes'
class DiaperChangeUpdate(PermissionRequiredMixin, UpdateView):
model = DiaperChange
model = models.DiaperChange
permission_required = ('core.change_diaperchange',)
form_class = DiaperChangeForm
form_class = forms.DiaperChangeForm
success_url = '/changes'
class DiaperChangeDelete(PermissionRequiredMixin, DeleteView):
model = DiaperChange
model = models.DiaperChange
permission_required = ('core.delete_diaperchange',)
success_url = '/changes'
class FeedingList(PermissionRequiredMixin, FilterView):
model = Feeding
model = models.Feeding
template_name = 'core/feeding_list.html'
permission_required = ('core.view_feeding',)
paginate_by = 10
@ -86,9 +97,9 @@ class FeedingList(PermissionRequiredMixin, FilterView):
class FeedingAdd(PermissionRequiredMixin, CreateView):
model = Feeding
model = models.Feeding
permission_required = ('core.add_feeding',)
form_class = FeedingForm
form_class = forms.FeedingForm
success_url = '/feedings'
def get_form_kwargs(self):
@ -99,20 +110,20 @@ class FeedingAdd(PermissionRequiredMixin, CreateView):
class FeedingUpdate(PermissionRequiredMixin, UpdateView):
model = Feeding
model = models.Feeding
permission_required = ('core.change_feeding',)
form_class = FeedingForm
form_class = forms.FeedingForm
success_url = '/feedings'
class FeedingDelete(PermissionRequiredMixin, DeleteView):
model = Feeding
model = models.Feeding
permission_required = ('core.delete_feeding',)
success_url = '/feedings'
class NoteList(PermissionRequiredMixin, FilterView):
model = Note
model = models.Note
template_name = 'core/note_list.html'
permission_required = ('core.view_note',)
paginate_by = 10
@ -120,27 +131,27 @@ class NoteList(PermissionRequiredMixin, FilterView):
class NoteAdd(PermissionRequiredMixin, CreateView):
model = Note
model = models.Note
permission_required = ('core.add_note',)
fields = ['child', 'note']
success_url = '/notes'
class NoteUpdate(PermissionRequiredMixin, UpdateView):
model = Note
model = models.Note
permission_required = ('core.change_note',)
fields = ['child', 'note']
success_url = '/notes'
class NoteDelete(PermissionRequiredMixin, DeleteView):
model = Note
model = models.Note
permission_required = ('core.delete_note',)
success_url = '/notes'
class SleepList(PermissionRequiredMixin, FilterView):
model = Sleep
model = models.Sleep
template_name = 'core/sleep_list.html'
permission_required = ('core.view_sleep',)
paginate_by = 10
@ -148,9 +159,9 @@ class SleepList(PermissionRequiredMixin, FilterView):
class SleepAdd(PermissionRequiredMixin, CreateView):
model = Sleep
model = models.Sleep
permission_required = ('core.add_sleep',)
form_class = SleepForm
form_class = forms.SleepForm
success_url = '/sleep'
def get_form_kwargs(self):
@ -161,20 +172,20 @@ class SleepAdd(PermissionRequiredMixin, CreateView):
class SleepUpdate(PermissionRequiredMixin, UpdateView):
model = Sleep
model = models.Sleep
permission_required = ('core.change_sleep',)
form_class = SleepForm
form_class = forms.SleepForm
success_url = '/sleep'
class SleepDelete(PermissionRequiredMixin, DeleteView):
model = Sleep
model = models.Sleep
permission_required = ('core.delete_sleep',)
success_url = '/sleep'
class TimerList(PermissionRequiredMixin, FilterView):
model = Timer
model = models.Timer
template_name = 'core/timer_list.html'
permission_required = ('core.view_timer',)
paginate_by = 10
@ -182,14 +193,14 @@ class TimerList(PermissionRequiredMixin, FilterView):
class TimerDetail(PermissionRequiredMixin, DetailView):
model = Timer
model = models.Timer
permission_required = ('core.view_timer',)
class TimerAdd(PermissionRequiredMixin, CreateView):
model = Timer
model = models.Timer
permission_required = ('core.add_timer',)
form_class = TimerForm
form_class = forms.TimerForm
success_url = '/timers'
def get_form_kwargs(self):
@ -199,9 +210,9 @@ class TimerAdd(PermissionRequiredMixin, CreateView):
class TimerUpdate(PermissionRequiredMixin, UpdateView):
model = Timer
model = models.Timer
permission_required = ('core.change_timer',)
form_class = TimerForm
form_class = forms.TimerForm
success_url = '/timers'
def get_form_kwargs(self):
@ -218,7 +229,7 @@ class TimerAddQuick(PermissionRequiredMixin, RedirectView):
permission_required = ('core.add_timer',)
def get(self, request, *args, **kwargs):
instance = Timer.objects.create(user=request.user)
instance = models.Timer.objects.create(user=request.user)
instance.save()
self.url = request.GET.get(
'next', reverse('timer-detail', args={instance.id}))
@ -229,7 +240,7 @@ class TimerRestart(PermissionRequiredMixin, RedirectView):
permission_required = ('core.change_timer',)
def get(self, request, *args, **kwargs):
instance = Timer.objects.get(id=kwargs['pk'])
instance = models.Timer.objects.get(id=kwargs['pk'])
instance.restart()
return super(TimerRestart, self).get(request, *args, **kwargs)
@ -241,7 +252,7 @@ class TimerStop(PermissionRequiredMixin, RedirectView):
permission_required = ('core.change_timer',)
def get(self, request, *args, **kwargs):
instance = Timer.objects.get(id=kwargs['pk'])
instance = models.Timer.objects.get(id=kwargs['pk'])
instance.stop()
return super(TimerStop, self).get(request, *args, **kwargs)
@ -250,13 +261,13 @@ class TimerStop(PermissionRequiredMixin, RedirectView):
class TimerDelete(PermissionRequiredMixin, DeleteView):
model = Timer
model = models.Timer
permission_required = ('core.delete_timer',)
success_url = '/'
class TummyTimeList(PermissionRequiredMixin, FilterView):
model = TummyTime
model = models.TummyTime
template_name = 'core/tummytime_list.html'
permission_required = ('core.view_tummytime',)
paginate_by = 10
@ -264,9 +275,9 @@ class TummyTimeList(PermissionRequiredMixin, FilterView):
class TummyTimeAdd(PermissionRequiredMixin, CreateView):
model = TummyTime
model = models.TummyTime
permission_required = ('core.add_tummytime',)
form_class = TummyTimeForm
form_class = forms.TummyTimeForm
success_url = '/tummy-time'
def get_form_kwargs(self):
@ -277,13 +288,13 @@ class TummyTimeAdd(PermissionRequiredMixin, CreateView):
class TummyTimeUpdate(PermissionRequiredMixin, UpdateView):
model = TummyTime
model = models.TummyTime
permission_required = ('core.change_tummytime',)
form_class = TummyTimeForm
form_class = forms.TummyTimeForm
success_url = '/tummy-time'
class TummyTimeDelete(PermissionRequiredMixin, DeleteView):
model = TummyTime
model = models.TummyTime
permission_required = ('core.delete_tummytime',)
success_url = '/tummy-time'

View File

@ -18,7 +18,6 @@
<a class="dropdown-item" href="{% url 'reports:report-diaperchange-lifetimes-child' object.slug %}">Diaper Lifetimes</a>
<a class="dropdown-item" href="{% url 'reports:report-sleep-pattern-child' object.slug %}">Sleep Pattern</a>
<a class="dropdown-item" href="{% url 'reports:report-sleep-totals-child' object.slug %}">Sleep Totals</a>
<a class="dropdown-item" href="{% url 'reports:report-timeline-child' object.slug %}">Timeline</a>
</div>
</div>

View File

@ -2,4 +2,3 @@ from .diaperchange_lifetimes import diaperchange_lifetimes # NOQA
from .diaperchange_types import diaperchange_types # NOQA
from .sleep_pattern import sleep_pattern # NOQA
from .sleep_totals import sleep_totals # NOQA
from .timeline import timeline # NOQA

View File

@ -1,42 +0,0 @@
{% extends 'reports/report_base.html' %}
{% load static %}
{% block title %}Timeline - {{ object }}{% endblock %}
{% block breadcrumbs %}
{{ block.super }}
<li class="breadcrumb-item active" aria-current="page">Timeline</li>
{% endblock %}
{% block content %}
<h1 class="text-center">Timeline</h1>
<h2 class="text-center text-muted">{{ object }}</h2>
<h3 class="text-center">
<a class="btn btn-sm btn-default" href="?date={{ date_previous|date:"Y-m-d" }}" aria-label="Previous">
<i class="icon icon-chevron-left" aria-hidden="true"></i>
<span class="sr-only">Previous</span>
</a>
{{ date|date }}
<a class="btn btn-sm btn-default" href="?date={{ date_next|date:"Y-m-d" }}" aria-label="Next">
<i class="icon icon-chevron-right" aria-hidden="true"></i>
<span class="sr-only">Next</span>
</a>
</h3>
<ul class="timeline m-auto">
{% for object in objects %}
<li{% cycle "" ' class="timeline-inverted"' %}>
<div class="timeline-badge {% if object.type == "start" %}bg-success{% elif object.type == "end" %}bg-danger{% else %}bg-info{% endif %}">
<i class="icon icon-{{ object.model_name }}"></i>
</div>
<div class="card text-right">
<div class="card-body">
{{ object.event }}
</div>
<div class="card-footer text-muted">
{{ object.time|timesince }} ago ({{ object.time|time }})
</div>
</div>
</li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -44,6 +44,3 @@ class ViewsTestCase(TestCase):
self.assertEqual(page.status_code, 200)
page = self.c.get('{}/sleep/totals/'.format(base_url))
self.assertEqual(page.status_code, 200)
page = self.c.get('{}/timeline/'.format(base_url))
self.assertEqual(page.status_code, 200)

View File

@ -19,8 +19,4 @@ urlpatterns = [
url(r'^children/(?P<slug>[^/.]+)/reports/sleep/totals/$',
views.SleepTotalsChildReport.as_view(),
name='report-sleep-totals-child'),
url(r'^children/(?P<slug>[^/.]+)/reports/timeline/$',
views.TimelineChildReport.as_view(),
name='report-timeline-child'),
]

View File

@ -3,12 +3,11 @@ from __future__ import unicode_literals
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views.generic.detail import DetailView
from django.utils import timezone
from core.models import Child, DiaperChange, Sleep
from .graphs import (diaperchange_types, diaperchange_lifetimes, sleep_pattern,
sleep_totals, timeline)
sleep_totals)
class DiaperChangeLifetimesChildReport(PermissionRequiredMixin, DetailView):
@ -89,22 +88,3 @@ class SleepTotalsChildReport(PermissionRequiredMixin, DetailView):
if instances:
context['html'], context['javascript'] = sleep_totals(instances)
return context
class TimelineChildReport(PermissionRequiredMixin, DetailView):
"""Chronological daily view of events (non-graph).
"""
model = Child
permission_required = ('core.view_child',)
template_name = 'reports/timeline.html'
def get_context_data(self, **kwargs):
context = super(TimelineChildReport, self).get_context_data(**kwargs)
date = self.request.GET.get('date', str(timezone.localdate()))
date = timezone.datetime.strptime(date, '%Y-%m-%d')
date = timezone.localtime(timezone.make_aware(date))
context['objects'] = timeline(self.object, date)
context['date'] = date
context['date_previous'] = date - timezone.timedelta(days=1)
context['date_next'] = date + timezone.timedelta(days=1)
return context