Merge pull request #250 from bornhack/feature/shift_planning
Shift planning
This commit is contained in:
commit
d132b5d2f2
|
@ -1,5 +1,5 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import Team, TeamMember, TeamTask
|
from .models import Team, TeamMember, TeamTask, TeamShift
|
||||||
from .email import add_added_membership_email, add_removed_membership_email
|
from .email import add_added_membership_email, add_removed_membership_email
|
||||||
from camps.utils import CampPropertyListFilter
|
from camps.utils import CampPropertyListFilter
|
||||||
|
|
||||||
|
@ -90,3 +90,10 @@ class TeamMemberAdmin(admin.ModelAdmin):
|
||||||
)
|
)
|
||||||
remove_member.description = 'Remove a user from the team.'
|
remove_member.description = 'Remove a user from the team.'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(TeamShift)
|
||||||
|
class TeamShiftAdmin(admin.ModelAdmin):
|
||||||
|
list_filter = [
|
||||||
|
'team',
|
||||||
|
]
|
||||||
|
|
43
src/teams/migrations/0043_auto_20180702_1338.py
Normal file
43
src/teams/migrations/0043_auto_20180702_1338.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# Generated by Django 2.0.4 on 2018-07-02 18:38
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields.ranges
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teams', '0042_auto_20180413_1933'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TeamShift',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated', models.DateTimeField(auto_now=True)),
|
||||||
|
('shift_range', django.contrib.postgres.fields.ranges.DateTimeRangeField()),
|
||||||
|
('people_required', models.IntegerField(default=1)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='team',
|
||||||
|
name='shifts_enabled',
|
||||||
|
field=models.BooleanField(default=False, help_text='Does this team have shifts? This enables defining shifts for this team.'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='teamshift',
|
||||||
|
name='team',
|
||||||
|
field=models.ForeignKey(help_text='The team this shift belongs to', on_delete=django.db.models.deletion.PROTECT, related_name='shifts', to='teams.Team'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='teamshift',
|
||||||
|
name='team_members',
|
||||||
|
field=models.ManyToManyField(to='teams.TeamMember'),
|
||||||
|
),
|
||||||
|
]
|
18
src/teams/migrations/0044_auto_20180702_1507.py
Normal file
18
src/teams/migrations/0044_auto_20180702_1507.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.0.4 on 2018-07-02 20:07
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teams', '0043_auto_20180702_1338'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='teamshift',
|
||||||
|
name='team_members',
|
||||||
|
field=models.ManyToManyField(blank=True, to='teams.TeamMember'),
|
||||||
|
),
|
||||||
|
]
|
14
src/teams/migrations/0045_merge_20180805_1131.py
Normal file
14
src/teams/migrations/0045_merge_20180805_1131.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# Generated by Django 2.0.4 on 2018-08-05 09:31
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('teams', '0044_auto_20180702_1507'),
|
||||||
|
('teams', '0043_auto_20180804_1641'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
]
|
|
@ -1,14 +1,16 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.signals import post_save
|
|
||||||
from django.dispatch import receiver
|
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from utils.models import CampRelatedModel
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.contrib.postgres.fields import DateTimeRangeField
|
from django.contrib.postgres.fields import DateTimeRangeField
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import logging
|
from django.contrib.postgres.fields import DateTimeRangeField
|
||||||
|
|
||||||
|
from utils.models import CampRelatedModel
|
||||||
|
|
||||||
logger = logging.getLogger("bornhack.%s" % __name__)
|
logger = logging.getLogger("bornhack.%s" % __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -103,6 +105,11 @@ class Team(CampRelatedModel):
|
||||||
help_text='Used to indicate to the IRC bot that this teams private IRC channel is in need of a permissions and ACL fix.'
|
help_text='Used to indicate to the IRC bot that this teams private IRC channel is in need of a permissions and ACL fix.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
shifts_enabled = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="Does this team have shifts? This enables defining shifts for this team."
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
unique_together = (('name', 'camp'), ('slug', 'camp'))
|
unique_together = (('name', 'camp'), ('slug', 'camp'))
|
||||||
|
@ -312,3 +319,40 @@ class TeamTask(CampRelatedModel):
|
||||||
self.slug = slugify(self.name)
|
self.slug = slugify(self.name)
|
||||||
super().save(**kwargs)
|
super().save(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class TeamShift(CampRelatedModel):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ("shift_range",)
|
||||||
|
|
||||||
|
team = models.ForeignKey(
|
||||||
|
'teams.Team',
|
||||||
|
related_name='shifts',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
help_text='The team this shift belongs to',
|
||||||
|
)
|
||||||
|
|
||||||
|
shift_range = DateTimeRangeField()
|
||||||
|
|
||||||
|
team_members = models.ManyToManyField(
|
||||||
|
TeamMember,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
people_required = models.IntegerField(
|
||||||
|
default=1
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def camp(self):
|
||||||
|
""" All CampRelatedModels must have a camp FK or a camp property """
|
||||||
|
return self.team.camp
|
||||||
|
|
||||||
|
camp_filter = 'team__camp'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{} team shift from {} to {}".format(
|
||||||
|
self.team.name,
|
||||||
|
self.shift_range.lower,
|
||||||
|
self.shift_range.upper
|
||||||
|
)
|
||||||
|
|
|
@ -37,6 +37,12 @@ Team: {{ team.name }} | {{ block.super }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li {% if view.active_menu == "shifts" %}class="active"{% endif %}>
|
||||||
|
<a href="{% url "teams:shifts" camp_slug=team.camp.slug team_slug=team.slug %}">
|
||||||
|
Shifts
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
{% if request.user in team.responsible_members.all %}
|
{% if request.user in team.responsible_members.all %}
|
||||||
<li {% if view.active_menu == "info_categories" %}class="active"{% endif %}>
|
<li {% if view.active_menu == "info_categories" %}class="active"{% endif %}>
|
||||||
<a href="{% url "teams:info_categories" camp_slug=team.camp.slug team_slug=team.slug %}">
|
<a href="{% url "teams:info_categories" camp_slug=team.camp.slug team_slug=team.slug %}">
|
||||||
|
|
10
src/teams/templates/team_shift_confirm_delete.html
Normal file
10
src/teams/templates/team_shift_confirm_delete.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{% extends 'team_base.html' %}
|
||||||
|
|
||||||
|
{% block team_content %}
|
||||||
|
|
||||||
|
<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 %}
|
16
src/teams/templates/team_shift_form.html
Normal file
16
src/teams/templates/team_shift_form.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{% extends 'team_base.html' %}
|
||||||
|
{% load commonmark %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block team_content %}
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
<button type="submit" class="btn btn-success">
|
||||||
|
{% if object.pk %}Update{% else %}Create{% endif %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
74
src/teams/templates/team_shift_list.html
Normal file
74
src/teams/templates/team_shift_list.html
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
{% extends 'team_base.html' %}
|
||||||
|
{% load commonmark %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block team_content %}
|
||||||
|
|
||||||
|
{% if request.user in team.responsible_members.all %}
|
||||||
|
<a class="btn btn-success"
|
||||||
|
href="{% url 'teams:shift_create' camp_slug=camp.slug team_slug=team.slug %}">
|
||||||
|
Create a single shift
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-success"
|
||||||
|
href="{% url 'teams:shift_create_multiple' camp_slug=camp.slug team_slug=team.slug %}">
|
||||||
|
Create multiple shifts
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<table class="table table-condensed">
|
||||||
|
<tbody>
|
||||||
|
{% for shift in shifts %}
|
||||||
|
{% ifchanged shift.shift_range.lower|date:'d' %}
|
||||||
|
<tr>
|
||||||
|
<td colspan=4>
|
||||||
|
<h4>
|
||||||
|
{{ shift.shift_range.lower|date:'Y-m-d l' }}
|
||||||
|
</h4>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
From
|
||||||
|
<th>
|
||||||
|
To
|
||||||
|
<th>
|
||||||
|
People required
|
||||||
|
<th>
|
||||||
|
People
|
||||||
|
<th>
|
||||||
|
Actions
|
||||||
|
{% endifchanged %}
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ shift.shift_range.lower|date:'H:i' }}
|
||||||
|
<td>
|
||||||
|
{{ shift.shift_range.upper|date:'H:i' }}
|
||||||
|
<td>
|
||||||
|
{{ shift.people_required }}
|
||||||
|
<td>
|
||||||
|
{% for member in shift.team_members.all %}
|
||||||
|
{{ member.user }}{% if not forloop.last %},{% endif %}
|
||||||
|
{% empty %}
|
||||||
|
None!
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<td>
|
||||||
|
{% if request.user in team.responsible_members.all %}
|
||||||
|
<a class="btn btn-info"
|
||||||
|
href="{% url 'teams:shift_update' camp_slug=camp.slug team_slug=team.slug pk=shift.pk %}">
|
||||||
|
<i class="fas fa-edit"></i> Edit
|
||||||
|
</a>
|
||||||
|
<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
|
||||||
|
</a>
|
||||||
|
{% 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 %}
|
||||||
|
</table>
|
||||||
|
{% endblock %}
|
|
@ -29,6 +29,15 @@ from teams.views.tasks import (
|
||||||
TaskUpdateView,
|
TaskUpdateView,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from teams.views.shifts import (
|
||||||
|
ShiftListView,
|
||||||
|
ShiftCreateView,
|
||||||
|
ShiftCreateMultipleView,
|
||||||
|
ShiftUpdateView,
|
||||||
|
ShiftDeleteView,
|
||||||
|
MemberTakesShift,
|
||||||
|
)
|
||||||
|
|
||||||
app_name = 'teams'
|
app_name = 'teams'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
@ -144,7 +153,41 @@ urlpatterns = [
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
)
|
),
|
||||||
|
path('shifts/', include([
|
||||||
|
path(
|
||||||
|
'',
|
||||||
|
ShiftListView.as_view(),
|
||||||
|
name="shifts"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'create/',
|
||||||
|
ShiftCreateView.as_view(),
|
||||||
|
name="shift_create"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'create_multiple/',
|
||||||
|
ShiftCreateMultipleView.as_view(),
|
||||||
|
name="shift_create_multiple"
|
||||||
|
),
|
||||||
|
path('<int:pk>/', include([
|
||||||
|
path(
|
||||||
|
'',
|
||||||
|
ShiftUpdateView.as_view(),
|
||||||
|
name="shift_update"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'delete',
|
||||||
|
ShiftDeleteView.as_view(),
|
||||||
|
name="shift_delete"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'take',
|
||||||
|
MemberTakesShift.as_view(),
|
||||||
|
name="shift_member_take"
|
||||||
|
),
|
||||||
|
])),
|
||||||
|
]))
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
306
src/teams/views/shifts.py
Normal file
306
src/teams/views/shifts.py
Normal file
|
@ -0,0 +1,306 @@
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.views.generic import (
|
||||||
|
View,
|
||||||
|
CreateView,
|
||||||
|
UpdateView,
|
||||||
|
ListView,
|
||||||
|
FormView,
|
||||||
|
DeleteView,
|
||||||
|
)
|
||||||
|
from django import forms
|
||||||
|
from django.contrib.postgres.forms.ranges import RangeWidget
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
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.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 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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 + 1):
|
||||||
|
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(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:shifts',
|
||||||
|
kwargs=kwargs
|
||||||
|
)
|
||||||
|
)
|
Loading…
Reference in a new issue