Fix multiple shifts create. Add deletion. Add a way to take a shift.

This commit is contained in:
Víðir Valberg Guðmundsson 2018-07-19 22:46:26 +02:00
parent 4f77b21a60
commit 2bdd172b92
5 changed files with 191 additions and 108 deletions

View file

@ -0,0 +1,16 @@
{% extends 'base.html' %}
{% block content %}
<a class="btn btn-info"
href="{% url 'teams:shift_list' camp_slug=camp.slug team_slug=team.slug %}">
<i class="fas fa-chevron-left"></i> Cancel
</a>
<hr />
<form method="post">{% csrf_token %}
<p>Are you sure you want to delete "{{ object }}"?</p>
<input type="submit" class="btn btn-danger" value="Confirm" />
</form>
{% endblock %}

View file

@ -12,10 +12,6 @@
<hr /> <hr />
{% if form.errors %}
{{ form.errors }}
{% endif %}
<form method="POST"> <form method="POST">
{% csrf_token %} {% csrf_token %}
{% bootstrap_form form %} {% bootstrap_form form %}

View file

@ -53,17 +53,29 @@
<td> <td>
{{ shift.people_required }} {{ shift.people_required }}
<td> <td>
{{ shift.team_members }} {% for member in shift.team_members.all %}
{{ member.user }}{% if not forloop.last %},{% endif %}
{% empty %}
None!
{% endfor %}
<td> <td>
{% if request.user in team.responsible_members.all %} {% if request.user in team.responsible_members.all %}
<a class="btn btn-info" <a class="btn btn-info"
href="{% url 'teams:shift_update' camp_slug=camp.slug team_slug=team.slug pk=shift.pk %}"> href="{% url 'teams:shift_update' camp_slug=camp.slug team_slug=team.slug pk=shift.pk %}">
<i class="fas fa-edit"></i> Edit <i class="fas fa-edit"></i> Edit
</a> </a>
<a class="btn btn-danger" href=""> <a class="btn btn-danger"
href="{% url 'teams:shift_delete' camp_slug=camp.slug team_slug=team.slug pk=shift.pk %}">
<i class="fas fa-trash"></i> Delete <i class="fas fa-trash"></i> Delete
</a> </a>
{% endif %} {% endif %}
{% if shift.people_required > shift.team_members.count %}
<a class="btn btn-success"
href="{% url 'teams:shift_member_take' camp_slug=camp.slug team_slug=team.slug pk=shift.pk %}">
<i class="fas fa-thumbs-up"></i> Take it!
</a>
{% endif %}
{% endfor %} {% endfor %}
</table> </table>
{% endblock %} {% endblock %}

View file

@ -23,6 +23,8 @@ from teams.views.shifts import (
ShiftCreateView, ShiftCreateView,
ShiftCreateMultipleView, ShiftCreateMultipleView,
ShiftUpdateView, ShiftUpdateView,
ShiftDeleteView,
MemberTakesShift,
) )
app_name = 'teams' app_name = 'teams'
@ -137,11 +139,23 @@ urlpatterns = [
ShiftCreateMultipleView.as_view(), ShiftCreateMultipleView.as_view(),
name="shift_create_multiple" name="shift_create_multiple"
), ),
path( path('<int:pk>/', include([
'<int:pk>/', path(
ShiftUpdateView.as_view(), '',
name="shift_update" ShiftUpdateView.as_view(),
) name="shift_update"
),
path(
'delete',
ShiftDeleteView.as_view(),
name="shift_delete"
),
path(
'take',
MemberTakesShift.as_view(),
name="shift_member_take"
),
])),
])) ]))
]), ]),
), ),

View file

@ -1,5 +1,13 @@
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import CreateView, UpdateView, ListView, FormView from django.http import HttpResponseRedirect
from django.views.generic import (
View,
CreateView,
UpdateView,
ListView,
FormView,
DeleteView,
)
from django import forms from django import forms
from django.contrib.postgres.forms.ranges import RangeWidget from django.contrib.postgres.forms.ranges import RangeWidget
from django.utils import timezone from django.utils import timezone
@ -11,6 +19,7 @@ from camps.mixins import CampViewMixin
from ..models import ( from ..models import (
Team, Team,
TeamMember,
TeamShift, TeamShift,
) )
@ -33,100 +42,103 @@ class ShiftListView(LoginRequiredMixin, CampViewMixin, ListView):
return context return context
def time_choices(): def date_choices(camp):
index = 0 index = 0
minute_choices = [] minute_choices = []
SHIFT_MINIMUM_LENGTH = 15 # TODO: Maybe this should be configurable per team? # To begin with we assume a shift can not be shorter than an hour
SHIFT_MINIMUM_LENGTH = 60
while index * SHIFT_MINIMUM_LENGTH < 60: while index * SHIFT_MINIMUM_LENGTH < 60:
minutes = int(index * SHIFT_MINIMUM_LENGTH) minutes = int(index * SHIFT_MINIMUM_LENGTH)
minute_choices.append(minutes) minute_choices.append(minutes)
index += 1 index += 1
time_choices = [] def get_time_choices(date):
for hour in range(0, 24): time_choices = []
for minute in minute_choices: for hour in range(0, 24):
choice_label = "{hour:02d}:{minutes:02d}".format(hour=hour, minutes=minute) for minute in minute_choices:
time_choices.append((choice_label, choice_label)) time_label = "{hour:02d}:{minutes:02d}".format(
hour=hour,
minutes=minute
)
choice_value = "{} {}".format(date, time_label)
time_choices.append((choice_value, choice_value))
return time_choices
return time_choices choices = []
current_date = camp.camp.lower.date()
while current_date != camp.camp.upper.date():
choices.append(
(
current_date,
get_time_choices(current_date.strftime("%Y-%m-%d"))
)
)
current_date += timezone.timedelta(days=1)
return choices
class ShiftForm(forms.ModelForm): class ShiftForm(forms.ModelForm):
class Meta: class Meta:
model = TeamShift model = TeamShift
fields = [ fields = [
'from_date', 'from_datetime',
'from_time', 'to_datetime',
'to_date',
'to_time',
'people_required' 'people_required'
] ]
def __init__(self, instance=None, **kwargs): def __init__(self, instance=None, **kwargs):
camp = kwargs.pop('camp')
super().__init__(instance=instance, **kwargs) super().__init__(instance=instance, **kwargs)
self.fields['from_datetime'].widget = forms.Select(choices=date_choices(camp))
self.fields['to_datetime'].widget = forms.Select(choices=date_choices(camp))
if instance: if instance:
current_tz = timezone.get_current_timezone() current_tz = timezone.get_current_timezone()
lower = instance.shift_range.lower.astimezone(current_tz).strftime("%Y-%m-%d %H:%M")
upper = instance.shift_range.upper.astimezone(current_tz).strftime("%Y-%m-%d %H:%M")
self.fields['from_datetime'].initial = lower
self.fields['to_datetime'].initial = upper
lower = instance.shift_range.lower.astimezone(current_tz) from_datetime = forms.DateTimeField()
upper = instance.shift_range.upper.astimezone(current_tz) to_datetime = forms.DateTimeField()
from_date = lower.strftime('%Y-%m-%d') def _get_from_datetime(self):
from_time = lower.strftime('%H:%M') current_timezone = timezone.get_current_timezone()
to_date = upper.strftime('%Y-%m-%d') return (
to_time = upper.strftime('%H:%M') self.cleaned_data['from_datetime']
.astimezone(current_timezone)
)
self.fields['from_date'].initial = from_date def _get_to_datetime(self):
self.fields['from_time'].initial = from_time current_timezone = timezone.get_current_timezone()
self.fields['to_date'].initial = to_date return (
self.fields['to_time'].initial = to_time self.cleaned_data['to_datetime']
.astimezone(current_timezone)
)
from_date = forms.DateField( def clean(self):
help_text="Format is YYYY-MM-DD" self.lower = self._get_from_datetime()
) self.upper = self._get_to_datetime()
if self.lower > self.upper:
from_time = forms.ChoiceField( raise forms.ValidationError('Start can not be after end.')
choices=time_choices
)
to_date = forms.DateField(
help_text="Format is YYYY-MM-DD"
)
to_time = forms.ChoiceField(
choices=time_choices
)
def save(self, commit=True): def save(self, commit=True):
from_string = "{} {}".format( # self has .lower and .upper from .clean()
self.cleaned_data['from_date'], self.instance.shift_range = DateTimeTZRange(self.lower, self.upper)
self.cleaned_data['from_time']
)
to_string = "{} {}".format(
self.cleaned_data['to_date'],
self.cleaned_data['to_time']
)
datetime_format = '%Y-%m-%d %H:%M'
current_timezone = timezone.get_current_timezone()
lower = (
timezone.datetime
.strptime(from_string, datetime_format)
.astimezone(current_timezone)
)
upper = (
timezone.datetime
.strptime(to_string, datetime_format)
.astimezone(current_timezone)
)
self.instance.shift_range = DateTimeTZRange(lower, upper)
return super().save(commit=commit) return super().save(commit=commit)
class ShiftCreateView(LoginRequiredMixin, CampViewMixin, CreateView): class ShiftCreateView(LoginRequiredMixin, CampViewMixin, CreateView):
model = TeamShift model = TeamShift
template_name = "shifts/shift_form.html" template_name = "shifts/shift_form.html"
form_class = ShiftForm form_class = ShiftForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['camp'] = self.camp
return kwargs
def form_valid(self, form): def form_valid(self, form):
shift = form.save(commit=False) shift = form.save(commit=False)
shift.team = Team.objects.get( shift.team = Team.objects.get(
@ -155,6 +167,11 @@ class ShiftUpdateView(LoginRequiredMixin, CampViewMixin, UpdateView):
template_name = "shifts/shift_form.html" template_name = "shifts/shift_form.html"
form_class = ShiftForm form_class = ShiftForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['camp'] = self.camp
return kwargs
def get_success_url(self): def get_success_url(self):
self.kwargs.pop('pk') self.kwargs.pop('pk')
return reverse( return reverse(
@ -168,26 +185,41 @@ class ShiftUpdateView(LoginRequiredMixin, CampViewMixin, UpdateView):
return context return context
class ShiftDeleteView(LoginRequiredMixin, CampViewMixin, DeleteView):
model = TeamShift
template_name = "shifts/shift_confirm_delete.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['team'] = Team.objects.get(
camp=self.camp,
slug=self.kwargs['team_slug']
)
return context
def get_success_url(self):
self.kwargs.pop('pk')
return reverse(
'teams:shift_list',
kwargs=self.kwargs
)
class MultipleShiftForm(forms.Form): class MultipleShiftForm(forms.Form):
date = forms.DateField( def __init__(self, instance=None, **kwargs):
help_text="Format is YYYY-MM-DD" camp = kwargs.pop('camp')
) super().__init__(**kwargs)
self.fields['from_datetime'].widget = forms.Select(choices=date_choices(camp))
from_datetime = forms.DateTimeField()
number_of_shifts = forms.IntegerField( number_of_shifts = forms.IntegerField(
help_text="How many shifts in a day?" help_text="How many shifts?"
) )
start_time = forms.TimeField( shift_length = forms.IntegerField(
help_text="When the first shift should start? Defaults to 00:00.", help_text="How long should a shift be in minutes?"
required=False,
initial="00:00"
)
end_time = forms.TimeField(
help_text="When the last shift should end? Defaults to 00:00 (next day).",
required=False,
initial="00:00"
) )
people_required = forms.IntegerField() people_required = forms.IntegerField()
@ -197,50 +229,38 @@ class ShiftCreateMultipleView(LoginRequiredMixin, CampViewMixin, FormView):
template_name = "shifts/shift_form.html" template_name = "shifts/shift_form.html"
form_class = MultipleShiftForm form_class = MultipleShiftForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['camp'] = self.camp
return kwargs
def form_valid(self, form): def form_valid(self, form):
team = Team.objects.get( team = Team.objects.get(
camp=self.camp, camp=self.camp,
slug=self.kwargs['team_slug'] slug=self.kwargs['team_slug']
) )
date = form.cleaned_data['date']
number_of_shifts = form.cleaned_data['number_of_shifts']
start_time = form.cleaned_data['start_time']
end_time = form.cleaned_data['end_time']
people_required = form.cleaned_data['people_required']
current_timezone = timezone.get_current_timezone() current_timezone = timezone.get_current_timezone()
# create start datetime
start_datetime = ( start_datetime = (
timezone.datetime.combine(date, start_time) form.cleaned_data['from_datetime']
.astimezone(current_timezone) .astimezone(current_timezone)
) )
# create end datetime number_of_shifts = form.cleaned_data['number_of_shifts']
if end_time == "00:00": shift_length = form.cleaned_data['shift_length']
# if end time is midnight, we want midnight for next day people_required = form.cleaned_data['people_required']
date = date + timezone.timedelta(days=1)
end_datetime = (
timezone.datetime.combine(date, end_time)
.astimezone(current_timezone)
)
# figure out minutes between start and end datetime
total_minutes = (end_datetime - start_datetime).total_seconds() / 60
# divide by number of shifts -> duration for each shift
shift_duration = total_minutes / number_of_shifts
shifts = [] shifts = []
for index in range(number_of_shifts + 1): for index in range(number_of_shifts + 1):
shift_range = DateTimeTZRange( shift_range = DateTimeTZRange(
start_datetime, start_datetime,
start_datetime + timezone.timedelta(minutes=shift_duration), start_datetime + timezone.timedelta(minutes=shift_length),
) )
shifts.append(TeamShift( shifts.append(TeamShift(
team=team, team=team,
people_required=people_required, people_required=people_required,
shift_range=shift_range shift_range=shift_range
)) ))
start_datetime += timezone.timedelta(minutes=shift_duration) start_datetime += timezone.timedelta(minutes=shift_length)
TeamShift.objects.bulk_create(shifts) TeamShift.objects.bulk_create(shifts)
@ -259,3 +279,28 @@ class ShiftCreateMultipleView(LoginRequiredMixin, CampViewMixin, FormView):
slug=self.kwargs['team_slug'] slug=self.kwargs['team_slug']
) )
return context return context
class MemberTakesShift(CampViewMixin, View):
http_methods = ['get']
def get(self, request, **kwargs):
shift = TeamShift.objects.get(id=kwargs['pk'])
team = Team.objects.get(
camp=self.camp,
slug=kwargs['team_slug']
)
team_member = TeamMember.objects.get(team=team, user=request.user)
shift.team_members.add(team_member)
kwargs.pop('pk')
return HttpResponseRedirect(
reverse(
'teams:shift_list',
kwargs=kwargs
)
)