Permalink
Browse files

[RV] Show availability charts for each item

  • Loading branch information...
Benestar committed Apr 10, 2017
1 parent f2ba83f commit 3842df10857cca23dbd4387b26c65ad5f49bedf1
@@ -1,7 +1,7 @@
from rental.models import Rental


def get_item_availability_list(start_date, return_date, depot_id, item_list):
def get_item_availability_intervals(start_date, return_date, depot_id, item_list):
"""
Calculate availability for each item in item_list
@@ -14,17 +14,15 @@ def get_item_availability_list(start_date, return_date, depot_id, item_list):
depot_id=depot_id,
state=Rental.STATE_APPROVED
)
availability_list = []
item_availability_intervals = []

for item in item_list:
intervals = get_availability_intervals(
start_date, return_date, item, rentals
)
availability_list.append(
(item, get_maximum_availability(intervals))
)
item_availability_intervals.append((item, intervals))

return availability_list
return item_availability_intervals


def get_availability_intervals(start, end, item, rentals):
@@ -37,7 +35,7 @@ def get_availability_intervals(start, end, item, rentals):
:param start: the beginning of the time frame
:param end: the end of the time frame
:param item: the item that you want to rent
:param rentals: a list of all rentals
:param rentals: a list of all rentals to consider
:return: a list of lists of the form [from, to, num_available]
"""

@@ -77,14 +75,11 @@ def get_availability_intervals(start, end, item, rentals):
return intervals


def get_maximum_availability(intervals):
def get_minimum_availability(intervals):
"""
Get the maximum quantity that is available in every interval
Get the minimum availability across all intervals
:author: Leo Tappe
:param intervals: the intervals for which availability has been calculated
:return: the minimum num_available value across all intervals
"""

min_interval = min(intervals, key=lambda x: x[2])
@@ -78,3 +78,25 @@ def extract_item_quantities(data):
item_quantities[int(m.group(1))] = int(quantity)

return item_quantities


def get_chart_data(intervals):
"""
Generate the data the JavaScript can render
:author: Benedikt Seidl
"""

data = []

for begin, end, availability in intervals:
data.append({
"x": begin.isoformat(),
"y": availability
})
data.append({
"x": end.isoformat(),
"y": availability
})

return data
@@ -66,10 +66,12 @@ <h1>New Rental <small>for {{ depot.name }}</small></h1>
<th>Selected</th>
</tr>

{% for item, availability in item_availability_list %}
<tr class="rental-item">
{% for item, intervals, availability in availability_data %}
<tr class="rental-item" data-name="{{ item.name }}" data-intervals="{{ intervals|tojson }}">
<td class="rental-item-name">{{ item.name }}</td>
<td class="rental-item-availability">{{ availability }}</td>
<td class="rental-item-availability">
<a href="#availability-modal" data-toggle="modal">{{ availability }}</a>
</td>
<td class="rental-item-location">{{ item.location }}</td>
{% if show_visibility %}
<td>{% item_visibility item.visibility %}</td>
@@ -107,4 +109,5 @@ <h1>New Rental <small>for {{ depot.name }}</small></h1>

{% include 'depot/modals/checkout-modal.html' %}
{% include 'depot/modals/date-modal.html' %}
{% include 'depot/modals/availability-modal.html' %}
{% endblock %}
@@ -0,0 +1,23 @@
<div class="modal fade" id="availability-modal" tabindex="-1"
role="dialog" aria-labelledby="availability-title">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" id="availability-title">
Availability <small>of <span id="availability-item"></span></small>
</h4>
</div>
<div class="modal-body">
<canvas id="availability-chart" height="200"></canvas>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
Close
</button>
</div>
</div>
</div>
</div>
@@ -104,17 +104,25 @@ def create_rental(request, depot_id):
start_date, return_date = helpers.get_start_return_date(request.GET)

item_list = helpers.get_item_list(depot, request.user)
item_availability_list = availability.get_item_availability_list(
item_availability_intervals = availability.get_item_availability_intervals(
start_date, return_date, depot_id, item_list
)

availability_data = []
for item, intervals in item_availability_intervals:
availability_data.append((
item,
helpers.get_chart_data(intervals),
availability.get_minimum_availability(intervals)
))

errors = request.session.pop('errors', None)
data = request.session.pop('data', {})

return render(request, 'depot/create-rental.html', {
'depot': depot,
'show_visibility': helpers.show_private_items(depot, request.user),
'item_availability_list': item_availability_list,
'availability_data': availability_data,
'errors': errors,
'data': data,
'item_quantities': helpers.extract_item_quantities(data),
@@ -12,6 +12,7 @@
"babel-loader": "^6.4.0",
"babel-preset-es2015": "^6.24.0",
"bootstrap-sass": "^3.3.7",
"chart.js": "^2.5.0",
"cross-env": "^3.2.4",
"css-loader": "^0.27.3",
"eonasdan-bootstrap-datetimepicker": "^4.17.47",
@@ -151,9 +151,16 @@ def create_items(rental, data):
'items': 'The rental cannot be submitted without any items.'
})

for item, avail in get_item_availability_list(rental, item_quantities):
item_list = Item.objects.filter(id__in=item_quantities.keys())

item_availability_intervals = availability.get_item_availability_intervals(
rental.start_date, rental.return_date, rental.depot_id, item_list
)

for item, intervals in item_availability_intervals:
try:
create_item_rental(rental, item, item_quantities[item.id], avail)
available = availability.get_minimum_availability(intervals)
create_item_rental(rental, item, item_quantities[item.id], available)
except ValidationError as e:
for key, value in e:
errors['%s %s' % (item.name, key)] = value
@@ -162,16 +169,8 @@ def create_items(rental, data):
raise ValidationError(errors)


def get_item_availability_list(rental, item_quantities):
item_list = Item.objects.filter(id__in=item_quantities.keys())

return availability.get_item_availability_list(
rental.start_date, rental.return_date, rental.depot_id, item_list
)


def create_item_rental(rental, item, quantity, availability):
if quantity > availability:
def create_item_rental(rental, item, quantity, available):
if quantity > available:
raise ValidationError({
'quantity': 'The quantity must not exceed the availability '
'of the item in the requested time frame.'
@@ -10,3 +10,4 @@ require('./login-form')
require('./date-form')
require('./number-input')
require('./rental-form')
require('./availability-chart')
@@ -0,0 +1,61 @@
let Chart = require('chart.js')

let availabilityChart = null

$('#availability-modal').on('show.bs.modal', (ev) => {
let name = $(ev.relatedTarget).parents('.rental-item').data('name')

$('#availability-item').text(name)
})

$('#availability-modal').on('shown.bs.modal', (ev) => {
let $chart = $('#availability-chart');
let intervals = $(ev.relatedTarget).parents('.rental-item').data('intervals')
let maxAvailability = intervals.reduce(
(val, acc) => val.y > acc.y ? val.y : acc.y
)

availabilityChart = new Chart($chart, {
type: 'line',
data: {
datasets: [{
label: 'Availability',
data: intervals,
backgroundColor: 'rgba(51, 122, 183, 0.4)',
borderColor: 'rgba(46, 109, 164, 1)',
steppedLine: true,
borderDashOffset: 0.0,
}]
},
options: {
layout: {
padding: {
left: 50
}
},
legend: {
position: 'bottom'
},
scales: {
xAxes: [{
type: 'time',
time: {
minUnit: 'hour'
}
}],
yAxes: [{
ticks: {
beginAtZero: true,
suggestedMax: maxAvailability + 1
}
}]
}
}
})
})

$('#availability-modal').on('hidden.bs.modal', (ev) => {
if (availabilityChart !== null) {
availabilityChart.destroy()
}
})
@@ -1,3 +1,4 @@
import json
from django import template
from django.http import request
from depot.models import Item
@@ -75,6 +76,17 @@ def key(dictionary, key):
return dictionary.get(key)


@register.filter
def tojson(object):
"""
Turn the given object into its JSON representation
:author: Benedikt Seidl
"""

return json.dumps(object)


@register.filter
def full_name(user):
"""

0 comments on commit 3842df1

Please sign in to comment.