general: add waiting list support (#11111)
This commit is contained in:
parent
40a83b3f4c
commit
5aeb24d58a
|
@ -16,6 +16,7 @@
|
|||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.db import transaction
|
||||
from django.db.models.expressions import RawSQL
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.text import slugify
|
||||
|
@ -51,23 +52,15 @@ class Agenda(models.Model):
|
|||
return reverse('chrono-manager-agenda-view', kwargs={'pk': self.id})
|
||||
|
||||
|
||||
class EventManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return super(EventManager, self).get_queryset().annotate(
|
||||
booked_places=RawSQL('''SELECT count(*)
|
||||
FROM agendas_booking
|
||||
WHERE agendas_booking.event_id = agendas_event.id
|
||||
AND cancellation_datetime IS NULL''', ()))
|
||||
|
||||
|
||||
class Event(models.Model):
|
||||
agenda = models.ForeignKey(Agenda)
|
||||
start_datetime = models.DateTimeField(_('Date/time'))
|
||||
places = models.PositiveIntegerField(_('Places'))
|
||||
waiting_list_places = models.PositiveIntegerField(
|
||||
_('Places in waiting list'), default=0)
|
||||
label = models.CharField(_('Label'), max_length=50, null=True, blank=True,
|
||||
help_text=_('Optional label to identify this date.'))
|
||||
|
||||
objects = EventManager()
|
||||
full = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
ordering = ['agenda', 'start_datetime']
|
||||
|
@ -77,11 +70,39 @@ class Event(models.Model):
|
|||
return self.label
|
||||
return date_format(localtime(self.start_datetime), format='DATETIME_FORMAT')
|
||||
|
||||
@property
|
||||
def booked_places(self):
|
||||
return self.booking_set.filter(cancellation_datetime__isnull=True,
|
||||
in_waiting_list=False).count()
|
||||
|
||||
@property
|
||||
def waiting_list(self):
|
||||
return self.booking_set.filter(cancellation_datetime__isnull=True,
|
||||
in_waiting_list=True).count()
|
||||
|
||||
|
||||
class Booking(models.Model):
|
||||
event = models.ForeignKey(Event)
|
||||
extra_data = JSONField(null=True)
|
||||
cancellation_datetime = models.DateTimeField(null=True)
|
||||
in_waiting_list = models.BooleanField(default=False)
|
||||
creation_datetime = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
with transaction.atomic():
|
||||
super(Booking, self).save(*args, **kwargs)
|
||||
initial_value = self.event.full
|
||||
self.event.full = bool(
|
||||
(self.event.booked_places >= self.event.places and
|
||||
self.event.waiting_list_places == 0) or
|
||||
self.event.waiting_list >= self.event.waiting_list_places)
|
||||
if self.event.full != initial_value:
|
||||
self.event.save()
|
||||
|
||||
def cancel(self):
|
||||
self.cancellation_datetime = now()
|
||||
self.save()
|
||||
|
||||
def accept(self):
|
||||
self.in_waiting_list = False
|
||||
self.save()
|
||||
|
|
|
@ -25,4 +25,5 @@ urlpatterns = patterns('',
|
|||
url(r'agenda/(?P<agenda_pk>\w+)/status/(?P<event_pk>\w+)/$', views.slot_status),
|
||||
url(r'booking/(?P<booking_pk>\w+)/$', views.booking),
|
||||
url(r'booking/(?P<booking_pk>\w+)/cancel/$', views.cancel_booking),
|
||||
url(r'booking/(?P<booking_pk>\w+)/accept/$', views.accept_booking),
|
||||
)
|
||||
|
|
|
@ -48,10 +48,9 @@ class Datetimes(GenericAPIView):
|
|||
response = {'data': [{
|
||||
'id': x.id,
|
||||
'text': unicode(x)}
|
||||
for x in Event.objects.filter(agenda=pk).filter(
|
||||
places__gt=F('booked_places'),
|
||||
start_datetime__gte=min_datetime)
|
||||
]}
|
||||
for x in Event.objects.filter(agenda=pk).filter(
|
||||
start_datetime__gte=min_datetime,
|
||||
full=False)]}
|
||||
return Response(response)
|
||||
|
||||
datetimes = Datetimes.as_view()
|
||||
|
@ -67,11 +66,26 @@ class Fillslot(GenericAPIView):
|
|||
|
||||
def post(self, request, agenda_pk=None, event_pk=None, format=None):
|
||||
event = Event.objects.filter(id=event_pk)[0]
|
||||
if event.booked_places >= event.places:
|
||||
return Response({'err': 1, 'reason': 'sold out'}, status.HTTP_400_BAD_REQUEST)
|
||||
new_booking = Booking(event_id=event_pk, extra_data=request.data)
|
||||
|
||||
if event.waiting_list_places:
|
||||
if event.waiting_list >= event.waiting_list_places:
|
||||
return Response({'err': 1, 'reason': 'sold out'}, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if event.booked_places >= event.places or event.waiting_list:
|
||||
# if this is full or there are people waiting, put new bookings
|
||||
# in the waiting list.
|
||||
new_booking.in_waiting_list = True
|
||||
else:
|
||||
if event.booked_places >= event.places:
|
||||
return Response({'err': 1, 'reason': 'sold out'}, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
new_booking.save()
|
||||
response = {'err': 0, 'booking_id': new_booking.id}
|
||||
response = {
|
||||
'err': 0,
|
||||
'in_waiting_list': new_booking.in_waiting_list,
|
||||
'booking_id': new_booking.id,
|
||||
}
|
||||
return Response(response)
|
||||
|
||||
fillslot = Fillslot.as_view()
|
||||
|
@ -105,6 +119,20 @@ class CancelBooking(APIView):
|
|||
cancel_booking = CancelBooking.as_view()
|
||||
|
||||
|
||||
class AcceptBooking(APIView):
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
|
||||
def post(self, request, booking_pk=None, format=None):
|
||||
booking = Booking.objects.get(id=booking_pk,
|
||||
cancellation_datetime__isnull=True,
|
||||
in_waiting_list=True)
|
||||
booking.accept()
|
||||
response = {'err': 0, 'booking_id': booking.id}
|
||||
return Response(response)
|
||||
|
||||
accept_booking = AcceptBooking.as_view()
|
||||
|
||||
|
||||
class SlotStatus(GenericAPIView):
|
||||
serializer_class = SlotSerializer
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
|
|
|
@ -25,9 +25,16 @@
|
|||
{% for event in object.event_set.all %}
|
||||
<li data-total="{{event.places}}" data-booked="{{event.booked_places}}"><a rel="popup" href="{% url 'chrono-manager-event-edit' pk=event.id %}">
|
||||
{% if event.label %}{{event.label}} / {% endif %}
|
||||
{% blocktrans with start=event.start_datetime places=event.places booked_places=event.booked_places %}
|
||||
{{ start }} ({{ places }} places, {{ booked_places }} booked places)
|
||||
{% endblocktrans %}</a>
|
||||
{{ event.start_datetime }}
|
||||
({% blocktrans with places=event.places booked_places=event.booked_places %}{{ places }} places, {{ booked_places }} booked places{% endblocktrans %}
|
||||
{% if event.waiting_list_places %}
|
||||
/
|
||||
{% blocktrans with places=event.waiting_list_places waiting_places=event.waiting_list %}
|
||||
{{waiting_places}} on {{ places }} in waiting list
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
)
|
||||
</a>
|
||||
<span class="occupation-bar"></span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
|
|
@ -63,7 +63,6 @@ def test_datetimes_api(app, some_data):
|
|||
assert 'data' in resp.json
|
||||
assert len(resp.json['data']) == 3
|
||||
|
||||
|
||||
def test_datetime_api_fr(app, some_data):
|
||||
agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id
|
||||
with override_settings(LANGUAGE_CODE='fr-fr'):
|
||||
|
@ -159,3 +158,79 @@ def test_status(app, some_data, user):
|
|||
assert resp.json['places']['total'] == 10
|
||||
assert resp.json['places']['available'] == 9
|
||||
assert resp.json['places']['reserved'] == 1
|
||||
|
||||
def test_waiting_list_datetimes(app, some_data, user):
|
||||
agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id
|
||||
event = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[0]
|
||||
event.waiting_list_places = 5
|
||||
event.save()
|
||||
|
||||
resp = app.get('/api/agenda/%s/datetimes/' % agenda_id)
|
||||
assert len(resp.json['data']) == 3
|
||||
assert event.id in [x['id'] for x in resp.json['data']]
|
||||
|
||||
for i in range(event.places):
|
||||
Booking(event=event).save()
|
||||
|
||||
# all places are booked but all the dates are still displayed as there is a
|
||||
# waiting list.
|
||||
resp = app.get('/api/agenda/%s/datetimes/' % agenda_id)
|
||||
assert len(resp.json['data']) == 3
|
||||
|
||||
# fill the waiting list
|
||||
for i in range(event.waiting_list_places):
|
||||
Booking(event=event, in_waiting_list=True).save()
|
||||
|
||||
# the event datetime should no longer be returned
|
||||
resp = app.get('/api/agenda/%s/datetimes/' % agenda_id)
|
||||
assert len(resp.json['data']) == 2
|
||||
assert not event.id in [x['id'] for x in resp.json['data']]
|
||||
|
||||
def test_waiting_list_booking(app, some_data, user):
|
||||
agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id
|
||||
event = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[0]
|
||||
event.waiting_list_places = 5
|
||||
event.save()
|
||||
|
||||
for i in range(event.places):
|
||||
Booking(event=event).save()
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event.id), status=200)
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['in_waiting_list'] is True
|
||||
|
||||
# cancel a booking that was not on the waiting list
|
||||
booking = Booking.objects.filter(event=event, in_waiting_list=False)[0]
|
||||
booking.cancel()
|
||||
|
||||
# check new booking is still done on the waiting list
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event.id), status=200)
|
||||
assert resp.json['err'] == 0
|
||||
assert resp.json['in_waiting_list'] is True
|
||||
|
||||
# fill the waiting list
|
||||
for i in range(event.waiting_list_places):
|
||||
Booking(event=event, in_waiting_list=True).save()
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event.id), status=400)
|
||||
assert resp.json['err'] == 1
|
||||
|
||||
def test_accept_booking(app, some_data, user):
|
||||
agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id
|
||||
event = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[0]
|
||||
event.waiting_list_places = 5
|
||||
event.save()
|
||||
|
||||
# create a booking on the waiting list
|
||||
booking = Booking(event=event, in_waiting_list=True)
|
||||
booking.save()
|
||||
|
||||
assert Booking.objects.filter(in_waiting_list=True).count() == 1
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
resp = app.post('/api/booking/%s/accept/' % booking.id)
|
||||
assert Booking.objects.filter(in_waiting_list=True).count() == 0
|
||||
assert Booking.objects.filter(in_waiting_list=False).count() == 1
|
||||
|
|
Loading…
Reference in New Issue