shift-selling #1
|
@ -13,7 +13,6 @@ from info.views import CampInfoView
|
|||
from people.views import PeopleView
|
||||
from sponsors.views import SponsorsView
|
||||
from villages.views import (
|
||||
VillageCreateView,
|
||||
VillageDeleteView,
|
||||
VillageDetailView,
|
||||
VillageListView,
|
||||
|
@ -118,11 +117,6 @@ urlpatterns = [
|
|||
include(
|
||||
[
|
||||
path("", VillageListView.as_view(), name="village_list"),
|
||||
path(
|
||||
"create/",
|
||||
VillageCreateView.as_view(),
|
||||
name="village_create",
|
||||
),
|
||||
path(
|
||||
"<slug:slug>/delete/",
|
||||
VillageDeleteView.as_view(),
|
||||
|
|
|
@ -401,7 +401,7 @@ class TeamShift(CampRelatedModel):
|
|||
|
||||
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)
|
||||
|
||||
|
@ -420,3 +420,21 @@ class TeamShift(CampRelatedModel):
|
|||
@property
|
||||
def users(self):
|
||||
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 }}
|
||||
<td>
|
||||
{% 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 %}
|
||||
None!
|
||||
{% 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 %}">
|
||||
<i class="fas fa-thumbs-down"></i> Unassign me
|
||||
</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"
|
||||
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
|
||||
|
|
|
@ -34,6 +34,10 @@ Your shifts | {{ block.super }}
|
|||
<td>
|
||||
{{ shift.shift_range.upper|date:'H:i' }}
|
||||
</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>
|
||||
<a class="btn btn-danger"
|
||||
href="{% url 'teams:shift_member_drop' camp_slug=camp.slug team_slug=shift.team.slug pk=shift.pk %}">
|
||||
|
|
|
@ -179,6 +179,11 @@ urlpatterns = [
|
|||
MemberDropsShift.as_view(),
|
||||
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}))
|
||||
)
|
||||
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)
|
||||
|
||||
kwargs.pop("pk")
|
||||
|
@ -297,6 +300,23 @@ class MemberDropsShift(LoginRequiredMixin, CampViewMixin, View):
|
|||
|
||||
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):
|
||||
template_name = "team_user_shifts.html"
|
||||
|
|
|
@ -16,10 +16,6 @@ Villages | {{ block.super }}
|
|||
tent, chairs and tables in the shop!
|
||||
</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 />
|
||||
{% if villages %}
|
||||
<table class="table table-hover table-condensed table-striped">
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from django.urls import path
|
||||
|
||||
from .views import (
|
||||
VillageCreateView,
|
||||
VillageDeleteView,
|
||||
VillageDetailView,
|
||||
VillageListView,
|
||||
|
@ -12,7 +11,7 @@ app_name = "villages"
|
|||
|
||||
urlpatterns = [
|
||||
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>/edit/", VillageUpdateView.as_view(), name="update"),
|
||||
path("<slug:slug>/", VillageDetailView.as_view(), name="detail"),
|
||||
|
|
Loading…
Reference in a new issue