313 lines
9.9 KiB
Python
313 lines
9.9 KiB
Python
from django import forms
|
|
from django.contrib import messages
|
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
from django.http import HttpResponseRedirect
|
|
from django.template import Context, Template
|
|
from django.urls import reverse
|
|
from django.utils import timezone
|
|
from django.views.generic import (
|
|
CreateView,
|
|
DeleteView,
|
|
FormView,
|
|
ListView,
|
|
TemplateView,
|
|
UpdateView,
|
|
View,
|
|
)
|
|
from psycopg2.extras import DateTimeTZRange
|
|
|
|
from camps.mixins import CampViewMixin
|
|
|
|
from ..models import Team, TeamMember, TeamShift
|
|
|
|
|
|
class ShiftListView(LoginRequiredMixin, CampViewMixin, ListView):
|
|
model = TeamShift
|
|
template_name = "team_shift_list.html"
|
|
context_object_name = "shifts"
|
|
active_menu = "shifts"
|
|
|
|
def get_queryset(self):
|
|
queryset = super().get_queryset()
|
|
return queryset.filter(team__slug=self.kwargs["team_slug"])
|
|
|
|
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 date_choices(camp):
|
|
|
|
index = 0
|
|
minute_choices = []
|
|
# To begin with we assume a shift can not be shorter than an hour
|
|
SHIFT_MINIMUM_LENGTH = 60
|
|
while index * SHIFT_MINIMUM_LENGTH < 60:
|
|
minutes = int(index * SHIFT_MINIMUM_LENGTH)
|
|
minute_choices.append(minutes)
|
|
index += 1
|
|
|
|
def get_time_choices(date):
|
|
time_choices = []
|
|
for hour in range(0, 24):
|
|
for minute in minute_choices:
|
|
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
|
|
|
|
choices = []
|
|
|
|
current_date = camp.buildup.lower.date()
|
|
while current_date != camp.teardown.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 Meta:
|
|
model = TeamShift
|
|
fields = ["from_datetime", "to_datetime", "people_required"]
|
|
|
|
def __init__(self, instance=None, **kwargs):
|
|
camp = kwargs.pop("camp")
|
|
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:
|
|
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
|
|
|
|
from_datetime = forms.DateTimeField()
|
|
to_datetime = forms.DateTimeField()
|
|
|
|
def _get_from_datetime(self):
|
|
current_timezone = timezone.get_current_timezone()
|
|
return self.cleaned_data["from_datetime"].astimezone(current_timezone)
|
|
|
|
def _get_to_datetime(self):
|
|
current_timezone = timezone.get_current_timezone()
|
|
return self.cleaned_data["to_datetime"].astimezone(current_timezone)
|
|
|
|
def clean(self):
|
|
self.lower = self._get_from_datetime()
|
|
self.upper = self._get_to_datetime()
|
|
if self.lower > self.upper:
|
|
raise forms.ValidationError("Start can not be after end.")
|
|
|
|
def save(self, commit=True):
|
|
# self has .lower and .upper from .clean()
|
|
self.instance.shift_range = DateTimeTZRange(self.lower, self.upper)
|
|
return super().save(commit=commit)
|
|
|
|
|
|
class ShiftCreateView(LoginRequiredMixin, CampViewMixin, CreateView):
|
|
model = TeamShift
|
|
template_name = "team_shift_form.html"
|
|
form_class = ShiftForm
|
|
active_menu = "shifts"
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs["camp"] = self.camp
|
|
return kwargs
|
|
|
|
def form_valid(self, form):
|
|
shift = form.save(commit=False)
|
|
shift.team = Team.objects.get(camp=self.camp, slug=self.kwargs["team_slug"])
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse("teams:shifts", kwargs=self.kwargs)
|
|
|
|
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
|
|
|
|
|
|
class ShiftUpdateView(LoginRequiredMixin, CampViewMixin, UpdateView):
|
|
model = TeamShift
|
|
template_name = "team_shift_form.html"
|
|
form_class = ShiftForm
|
|
active_menu = "shifts"
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs["camp"] = self.camp
|
|
return kwargs
|
|
|
|
def get_success_url(self):
|
|
self.kwargs.pop("pk")
|
|
return reverse("teams:shifts", kwargs=self.kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context["team"] = self.object.team
|
|
return context
|
|
|
|
|
|
class ShiftDeleteView(LoginRequiredMixin, CampViewMixin, DeleteView):
|
|
model = TeamShift
|
|
template_name = "team_shift_confirm_delete.html"
|
|
active_menu = "shifts"
|
|
|
|
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:shifts", kwargs=self.kwargs)
|
|
|
|
|
|
class MultipleShiftForm(forms.Form):
|
|
def __init__(self, instance=None, **kwargs):
|
|
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(help_text="How many shifts?")
|
|
|
|
shift_length = forms.IntegerField(
|
|
help_text="How long should a shift be in minutes?"
|
|
)
|
|
|
|
people_required = forms.IntegerField()
|
|
|
|
|
|
class ShiftCreateMultipleView(LoginRequiredMixin, CampViewMixin, FormView):
|
|
template_name = "team_shift_form.html"
|
|
form_class = MultipleShiftForm
|
|
active_menu = "shifts"
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs["camp"] = self.camp
|
|
return kwargs
|
|
|
|
def form_valid(self, form):
|
|
team = Team.objects.get(camp=self.camp, slug=self.kwargs["team_slug"])
|
|
current_timezone = timezone.get_current_timezone()
|
|
|
|
start_datetime = form.cleaned_data["from_datetime"].astimezone(current_timezone)
|
|
number_of_shifts = form.cleaned_data["number_of_shifts"]
|
|
shift_length = form.cleaned_data["shift_length"]
|
|
people_required = form.cleaned_data["people_required"]
|
|
|
|
shifts = []
|
|
for index in range(number_of_shifts):
|
|
shift_range = DateTimeTZRange(
|
|
start_datetime,
|
|
start_datetime + timezone.timedelta(minutes=shift_length),
|
|
)
|
|
shifts.append(
|
|
TeamShift(
|
|
team=team, people_required=people_required, shift_range=shift_range
|
|
)
|
|
)
|
|
start_datetime += timezone.timedelta(minutes=shift_length)
|
|
|
|
TeamShift.objects.bulk_create(shifts)
|
|
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse("teams:shifts", kwargs=self.kwargs)
|
|
|
|
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
|
|
|
|
|
|
class MemberTakesShift(LoginRequiredMixin, 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)
|
|
|
|
overlapping_shifts = TeamShift.objects.filter(
|
|
team__camp=self.camp,
|
|
team_members__user=request.user,
|
|
shift_range__overlap=shift.shift_range,
|
|
)
|
|
|
|
if overlapping_shifts.exists():
|
|
template = Template(
|
|
"""You have shifts overlapping with the one you are trying to assign:<br/> <ul>
|
|
{% for shift in shifts %}
|
|
<li>{{ shift }}</li>
|
|
{% endfor %}
|
|
</ul>
|
|
"""
|
|
)
|
|
messages.error(
|
|
request, template.render(Context({"shifts": overlapping_shifts}))
|
|
)
|
|
else:
|
|
shift.team_members.add(team_member)
|
|
|
|
kwargs.pop("pk")
|
|
|
|
return HttpResponseRedirect(reverse("teams:shifts", kwargs=kwargs))
|
|
|
|
|
|
class MemberDropsShift(LoginRequiredMixin, 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.remove(team_member)
|
|
|
|
kwargs.pop("pk")
|
|
|
|
return HttpResponseRedirect(reverse("teams:shifts", kwargs=kwargs))
|
|
|
|
|
|
class UserShifts(CampViewMixin, TemplateView):
|
|
template_name = "team_user_shifts.html"
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context["user_teams"] = self.request.user.teammember_set.filter(
|
|
team__camp=self.camp
|
|
)
|
|
context["user_shifts"] = TeamShift.objects.filter(
|
|
team__camp=self.camp, team_members__user=self.request.user
|
|
)
|
|
return context
|