shift-selling #1
|
@ -13,7 +13,6 @@ from info.views import CampInfoView
|
||||||
from people.views import PeopleView
|
from people.views import PeopleView
|
||||||
from sponsors.views import SponsorsView
|
from sponsors.views import SponsorsView
|
||||||
from villages.views import (
|
from villages.views import (
|
||||||
VillageCreateView,
|
|
||||||
VillageDeleteView,
|
VillageDeleteView,
|
||||||
VillageDetailView,
|
VillageDetailView,
|
||||||
VillageListView,
|
VillageListView,
|
||||||
|
@ -118,11 +117,6 @@ urlpatterns = [
|
||||||
include(
|
include(
|
||||||
[
|
[
|
||||||
path("", VillageListView.as_view(), name="village_list"),
|
path("", VillageListView.as_view(), name="village_list"),
|
||||||
path(
|
|
||||||
"create/",
|
|
||||||
VillageCreateView.as_view(),
|
|
||||||
name="village_create",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"<slug:slug>/delete/",
|
"<slug:slug>/delete/",
|
||||||
VillageDeleteView.as_view(),
|
VillageDeleteView.as_view(),
|
||||||
|
|
|
@ -401,7 +401,7 @@ class TeamShift(CampRelatedModel):
|
||||||
|
|
||||||
shift_range = DateTimeRangeField()
|
shift_range = DateTimeRangeField()
|
||||||
|
|
||||||
team_members = models.ManyToManyField(TeamMember, blank=True)
|
team_members = models.ManyToManyField(TeamMember, blank=True, through=TeamShiftAssignment)
|
||||||
|
|
||||||
people_required = models.IntegerField(default=1)
|
people_required = models.IntegerField(default=1)
|
||||||
|
|
||||||
|
@ -420,3 +420,21 @@ class TeamShift(CampRelatedModel):
|
||||||
@property
|
@property
|
||||||
def users(self):
|
def users(self):
|
||||||
return [member.user for member in self.team_members.all()]
|
return [member.user for member in self.team_members.all()]
|
||||||
|
|
||||||
|
class TeamShiftAssignment(CampRelatedModel):
|
||||||
|
team_shift = models.ForeignKey(
|
||||||
|
"teams.TeamShift",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
help_text="The shift",
|
||||||
|
)
|
||||||
|
|
||||||
|
team_member = models.ForeignKey(
|
||||||
|
"teams.TeamMember",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
help_text="The team member on shift",
|
||||||
|
)
|
||||||
|
|
||||||
|
for_sale = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="Is the shift assignment for sale?",
|
||||||
|
)
|
||||||
|
|
|
@ -50,7 +50,7 @@ Shifts | {{ block.super }}
|
||||||
{{ shift.people_required }}
|
{{ shift.people_required }}
|
||||||
<td>
|
<td>
|
||||||
{% for member in shift.team_members.all %}
|
{% for member in shift.team_members.all %}
|
||||||
{{ member.user.profile.get_public_credit_name }}{% if not forloop.last %},{% endif %}
|
{{ member.user.profile.get_public_credit_name }}{% if member.for_sale %} <em>(for sale!)</em>{% endif %}{% if not forloop.last %},{% endif %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
None!
|
None!
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -71,7 +71,11 @@ Shifts | {{ block.super }}
|
||||||
href="{% url 'teams:shift_member_drop' camp_slug=camp.slug team_slug=team.slug pk=shift.pk %}">
|
href="{% url 'teams:shift_member_drop' camp_slug=camp.slug team_slug=team.slug pk=shift.pk %}">
|
||||||
<i class="fas fa-thumbs-down"></i> Unassign me
|
<i class="fas fa-thumbs-down"></i> Unassign me
|
||||||
</a>
|
</a>
|
||||||
{% elif shift.people_required > shift.team_members.count %}
|
<a class="btn btn-warning"
|
||||||
|
href="{% url 'teams:shift_member_sell' camp_slug=camp.slug team_slug=team.slug pk=shift.pk %}">
|
||||||
|
<i class="fas fa-thumbs-down"></i> Sell shift
|
||||||
|
</a>
|
||||||
|
{% elif shift.people_required > shift.team_members.filter(for_sale=False).count %}
|
||||||
<a class="btn btn-success"
|
<a class="btn btn-success"
|
||||||
href="{% url 'teams:shift_member_take' camp_slug=camp.slug team_slug=team.slug pk=shift.pk %}">
|
href="{% url 'teams:shift_member_take' camp_slug=camp.slug team_slug=team.slug pk=shift.pk %}">
|
||||||
<i class="fas fa-thumbs-up"></i> Assign me
|
<i class="fas fa-thumbs-up"></i> Assign me
|
||||||
|
|
|
@ -34,6 +34,10 @@ Your shifts | {{ block.super }}
|
||||||
<td>
|
<td>
|
||||||
{{ shift.shift_range.upper|date:'H:i' }}
|
{{ shift.shift_range.upper|date:'H:i' }}
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<a class="btn btn-warning"
|
||||||
|
href="{% url 'teams:shift_member_sell' camp_slug=camp.slug team_slug=shift.team.slug pk=shift.pk %}">
|
||||||
|
<i class="fas fa-thumbs-down"></i> Sell shift
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn-danger"
|
<a class="btn btn-danger"
|
||||||
href="{% url 'teams:shift_member_drop' camp_slug=camp.slug team_slug=shift.team.slug pk=shift.pk %}">
|
href="{% url 'teams:shift_member_drop' camp_slug=camp.slug team_slug=shift.team.slug pk=shift.pk %}">
|
||||||
|
@ -45,4 +49,4 @@ Your shifts | {{ block.super }}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -179,6 +179,11 @@ urlpatterns = [
|
||||||
MemberDropsShift.as_view(),
|
MemberDropsShift.as_view(),
|
||||||
name="shift_member_drop",
|
name="shift_member_drop",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"sell",
|
||||||
|
MemberSellsShift.as_view(),
|
||||||
|
name="shift_member_sell",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -274,6 +274,9 @@ class MemberTakesShift(LoginRequiredMixin, CampViewMixin, View):
|
||||||
request, template.render(Context({"shifts": overlapping_shifts}))
|
request, template.render(Context({"shifts": overlapping_shifts}))
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
# Remove at most one shift assignment for sale
|
||||||
|
for shift_assignment in shift.team_members.filter(for_sale=True)[:1]:
|
||||||
|
shift_assignment.delete()
|
||||||
shift.team_members.add(team_member)
|
shift.team_members.add(team_member)
|
||||||
|
|
||||||
kwargs.pop("pk")
|
kwargs.pop("pk")
|
||||||
|
@ -297,6 +300,23 @@ class MemberDropsShift(LoginRequiredMixin, CampViewMixin, View):
|
||||||
|
|
||||||
return HttpResponseRedirect(reverse("teams:shifts", kwargs=kwargs))
|
return HttpResponseRedirect(reverse("teams:shifts", kwargs=kwargs))
|
||||||
|
|
||||||
|
class MemberSellsShift(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_assignment = shift.team_members.get(team_member = team_member.pk)
|
||||||
|
shift_assignment.for_sale = True
|
||||||
|
shift_assignment.save()
|
||||||
|
|
||||||
|
kwargs.pop("pk")
|
||||||
|
|
||||||
|
return HttpResponseRedirect(reverse("teams:shifts", kwargs=kwargs))
|
||||||
|
|
||||||
class UserShifts(CampViewMixin, TemplateView):
|
class UserShifts(CampViewMixin, TemplateView):
|
||||||
template_name = "team_user_shifts.html"
|
template_name = "team_user_shifts.html"
|
||||||
|
|
|
@ -16,10 +16,6 @@ Villages | {{ block.super }}
|
||||||
tent, chairs and tables in the shop!
|
tent, chairs and tables in the shop!
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if user.is_authenticated and not camp.read_only %}
|
|
||||||
<a href="{% url 'village_create' camp_slug=camp.slug %}" class="btn btn-primary">Create a new {{ camp.title }} village</a>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
{% if villages %}
|
{% if villages %}
|
||||||
<table class="table table-hover table-condensed table-striped">
|
<table class="table table-hover table-condensed table-striped">
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from .views import (
|
from .views import (
|
||||||
VillageCreateView,
|
|
||||||
VillageDeleteView,
|
VillageDeleteView,
|
||||||
VillageDetailView,
|
VillageDetailView,
|
||||||
VillageListView,
|
VillageListView,
|
||||||
|
@ -12,7 +11,7 @@ app_name = "villages"
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", VillageListView.as_view(), name="list"),
|
path("", VillageListView.as_view(), name="list"),
|
||||||
path("create/", VillageCreateView.as_view(), name="create"),
|
# path("create/", VillageCreateView.as_view(), name="create"),
|
||||||
path("<slug:slug>/delete/", VillageDeleteView.as_view(), name="delete"),
|
path("<slug:slug>/delete/", VillageDeleteView.as_view(), name="delete"),
|
||||||
path("<slug:slug>/edit/", VillageUpdateView.as_view(), name="update"),
|
path("<slug:slug>/edit/", VillageUpdateView.as_view(), name="update"),
|
||||||
path("<slug:slug>/", VillageDetailView.as_view(), name="detail"),
|
path("<slug:slug>/", VillageDetailView.as_view(), name="detail"),
|
||||||
|
|
Loading…
Reference in a new issue