Skip to content

Added ability to batch approve/deny requests #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 72 additions & 31 deletions hardwarecheckout/controllers/request.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
from hardwarecheckout import app
from hardwarecheckout import socketio
from hardwarecheckout.models import db

from hardwarecheckout.models.request import Request, RequestStatus
from hardwarecheckout.models.inventory_entry import InventoryEntry
from hardwarecheckout.models.inventory_entry import ItemType
from hardwarecheckout.models.user import User
from hardwarecheckout.models.item import Item
from hardwarecheckout.models.request_item import RequestItem
from hardwarecheckout.models.socket import Socket

from hardwarecheckout.utils import requires_auth, requires_admin, verify_token
from sqlalchemy import event

Expand All @@ -32,22 +30,22 @@ def get_requests():
RequestStatus = RequestStatus,
lottery_items = InventoryEntry.query.filter_by(item_type=ItemType.LOTTERY).all(),
user=user)

@app.route('/request/submit', methods=['POST'])
@requires_auth()
def request_submit():
"""Submits new request"""
if not (user.location and user.phone):
return jsonify(
success=False,
message="""Please fill out your <a href='/user'>user info</a> before
message="""Please fill out your <a href='/user'>user info</a> before
requesting items!"""
)
proposal = request.form.get('proposal', '')
requested_quantity = int(request.form.get('quantity', 1))

if app.config['LOTTERY_CHAR_LIMIT']:
if len(proposal) > app.config['LOTTERY_CHAR_LIMIT']:
if len(proposal) > app.config['LOTTERY_CHAR_LIMIT']:
proposal = proposal[:app.config['LOTTERY_CHAR_LIMIT']]

entry = InventoryEntry.query.get(request.form['item_id'])
Expand Down Expand Up @@ -107,20 +105,20 @@ def request_submit():
db.session.commit()
return jsonify(
success=True,
)
)

@app.route('/request/<int:id>/cancel', methods=['POST'])
@requires_auth()
def request_cancel(id):
"""Cancel request, returns status
Non-admins can only cancel own request, returns 403 if attempted"""
r = Request.query.get(id)
r = Request.query.get(id)
if user.is_admin or r.user_id == user.id:
r.status = RequestStatus.CANCELLED
db.session.commit()
return jsonify(
success=True,
)
)
else:
return jsonify(
success=False,
Expand All @@ -136,7 +134,7 @@ def request_update(id, status):
r = Request.query.get(id)
r.status = status
db.session.commit()
return True
return True

@app.route('/request/<int:id>/approve', methods=['POST'])
@requires_admin()
Expand All @@ -147,18 +145,49 @@ def request_approve(id):
entry = request_item.entry
quantity = request_item.quantity

# get items of proper type
for _ in xrange(quantity):
if entry.quantity < quantity:
return jsonify(
success=False,
message='Out of stock!'
)
if entry.quantity < quantity:
return jsonify(
success=False,
message='Out of stock!'
)
request_update(id, RequestStatus.APPROVED)
return jsonify(
success=True,
)

@app.route('/request/approve', methods=['POST'])
@requires_admin()
def approve_requests():
"""Approve all requests"""
submitted_requests = Request.query.filter_by(requires_lottery = False,
status = RequestStatus.SUBMITTED).all()

entries_dict = {}
for request in submitted_requests:
r = Request.query.get(request.id)
for request_item in r.items:
entry = request_item.entry
quantity = request_item.quantity

if entry in entries_dict:
entries_dict[entry] += quantity
else: entries_dict[entry] = quantity

for entry in entries_dict.keys():
requested_quantity = entries_dict[entry]
if entry.quantity < requested_quantity:
return jsonify(
success=False,
message='Some items are out of stock!'
)

for request in submitted_requests:
request_update(request.id, RequestStatus.APPROVED)

return jsonify(
success=True,
)

@app.route('/request/<int:id>/fulfill', methods=['POST'])
@requires_admin()
def request_fulfill(id):
Expand All @@ -182,8 +211,8 @@ def request_fulfill(id):
message='Out of stock!'
)
# give user item
r.user.items.append(item)
r.user.items.append(item)

# update request status
request_update(id, RequestStatus.FULFILLED)

Expand All @@ -203,6 +232,18 @@ def request_deny(id):
success=True,
)

@app.route('/request/deny', methods=['POST'])
@requires_admin()
def deny_requests():
"""Deny all requests"""
submitted_requests = Request.query.filter_by(requires_lottery = False,
status = RequestStatus.SUBMITTED).all()
for request in submitted_requests:
request_update(request.id, RequestStatus.DENIED)
return jsonify(
success=True,
)

@socketio.on('connect', namespace='/admin')
def authenticate_admin_conection():
"""Callback when client connects to /admin namespace, returns True
Expand All @@ -211,15 +252,15 @@ def authenticate_admin_conection():
if 'jwt' in request.cookies:
quill_id = verify_token(request.cookies['jwt'])
if not quill_id:
return False
return False
user = User.query.filter_by(quill_id=quill_id).first()

if user == None or not user.is_admin:
return False
return False

return True
else:
return False
else:
return False

@socketio.on('connect', namespace='/user')
def authenticate_user_conection():
Expand All @@ -229,18 +270,18 @@ def authenticate_user_conection():
if 'jwt' in request.cookies:
quill_id = verify_token(request.cookies['jwt'])
if not quill_id:
return False
return False
user = User.query.filter_by(quill_id=quill_id).first()

if user == None:
return False
return False

socket = Socket(request.sid, user)
db.session.add(socket)
db.session.commit()
return True
else:
return False
return False

@socketio.on('disconnect', namespace='/user')
def user_disconnect():
Expand All @@ -259,7 +300,7 @@ def on_request_update(mapper, connection, target):

def request_change_handler(target):
"""Handler that sends updated HTML for rendering requests"""
user = target.user
user = target.user
sockets = Socket.query.filter_by(user=user).all()

requests = Request.query.filter(Request.user == user, Request.status.in_(
Expand All @@ -277,14 +318,14 @@ def request_change_handler(target):
}, namespace='/user', room=socket.sid)

# TODO: add check if at least one admin is connected
approved_requests = render_template('includes/macros/display_requests.html',
approved_requests = render_template('includes/macros/display_requests.html',
# display requests that are submitted and non lottery OR already approved
requests = Request.query.filter_by(status = RequestStatus.APPROVED).all(),
RequestStatus = RequestStatus,
admin = True,
time = True)

submitted_requests = render_template('includes/macros/display_requests.html',
submitted_requests = render_template('includes/macros/display_requests.html',
# display requests that are submitted and non lottery OR already approved
requests = Request.query.filter_by(requires_lottery = False,
status = RequestStatus.SUBMITTED).all(),
Expand All @@ -295,18 +336,18 @@ def request_change_handler(target):
lottery_items = InventoryEntry.query.filter_by(item_type=ItemType.LOTTERY).all()
lottery_quantities = []
for item in lottery_items:
lottery_quantities.append(
lottery_quantities.append(
{
"id": item.id,
"available": item.quantity,
"available": item.quantity,
"submitted": item.submitted_request_quantity
}
)

socketio.emit('update', {
'approved_requests': approved_requests,
'submitted_requests': submitted_requests,
'lottery_quantities': lottery_quantities
'lottery_quantities': lottery_quantities
}, namespace='/admin')

# listeners for change to Request database
Expand Down
34 changes: 31 additions & 3 deletions hardwarecheckout/static/scripts/admin.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
var socket = io.connect(location.protocol + '//' + document.domain + ':'
var socket = io.connect(location.protocol + '//' + document.domain + ':'
+ location.port + '/admin');
socket.on('connect', function() {
console.log('Socket connected!')
Expand Down Expand Up @@ -29,7 +29,7 @@ socket.on('update', function(data) {
}
});

function init_request_actions() {
function init_request_actions() {
$('.request-action').api({
method: 'POST',
onSuccess: function(response) {
Expand All @@ -43,6 +43,34 @@ function init_request_actions() {

$(document).ready(function() {
init_request_actions();
$('.approve-all.button').api({
method: 'POST',
onSuccess: function(response) {
},
onFailure: function(err) {
console.log(err);
alert(err.message)
},
onError: function(err) {
console.log("ERROR!");
console.log(err);
}
});

$('.deny-all.button').api({
method: 'POST',
onSuccess: function(response) {
},
onFailure: function(err) {
console.log("ERROR!");
console.log(err);
},
onError: function(err) {
console.log("ERROR!");
console.log(err);
}
});

$('.run-lottery.button').api({
method: 'POST',
onSuccess: function(response) {
Expand All @@ -63,4 +91,4 @@ $(document).ready(function() {
onSuccess: function(response) {
},
});
});
});
6 changes: 4 additions & 2 deletions hardwarecheckout/static/scripts/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ $.fn.api.settings.api = {
'add item' : '/inventory/add',
'import items' : '/inventory/autoadd',
'update item' : '/inventory/update/{id}',
'return item' : '/inventory/return/{id}',
'return item' : '/inventory/return/{id}',
'delete item' : '/inventory/delete/{id}',
'add subitem' : '/inventory/subitem/add/{id}',
'update subitem' : '/inventory/subitem/update/{id}',
Expand All @@ -14,7 +14,9 @@ $.fn.api.settings.api = {
'approve request' : '/request/{id}/approve',
'fulfill request' : '/request/{id}/fulfill',
'deny request' : '/request/{id}/deny',
'update user' : '/user/{id}/update'
'update user' : '/user/{id}/update',
'approve all' : '/request/approve',
'deny all' : '/request/deny'
}

$.fn.api.settings.successTest = function(response) {
Expand Down
26 changes: 19 additions & 7 deletions hardwarecheckout/templates/pages/admin.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,19 @@ <h2> Approved Requests </h2>
{{ display_requests(approved_requests, RequestStatus, user.is_admin) }}
</div>

<h2> Pending Requests </h2>
<table class="ui very basic table">
<thead>
<tr>
<td>
<h2> Pending Requests </h2>
</td>
<td class="ui right aligned">
<div class="ui primary approve-all button" data-action="approve all">Approve All</div>
<div class="ui primary deny-all button" data-action="deny all">Deny All</div>
</td>
</tr>
</thead>
</table>
<div id="submitted_requests">
{{ display_requests(submitted_requests, RequestStatus, user.is_admin) }}
</div>
Expand All @@ -32,19 +44,19 @@ <h2> Lotteries </h2>
<td>
<div data-item-id="{{ item.id }}">
<h3> {{ item.name }} </h3>
Quantity:
Quantity:
<span class="item-quantity">
{{ item.quantity }}
</span>,
Proposals:
</span>,
Proposals:
<span class="submitted-quantity">
{{ item.submitted_request_quantity }}
</span>
<a href="{{ url_for('inventory_display', id=item.id) }}"> (view) </a>
<a href="{{ url_for('inventory_display', id=item.id) }}"> (view) </a>
</div>
</td>
<td class="ui right aligned">
<div class="ui primary run-lottery button"
<div class="ui primary run-lottery button"
data-action="run lottery" data-id="{{ item.id }}">Run Lottery</div>
</td>
</tr>
Expand All @@ -57,4 +69,4 @@ <h3> {{ item.name }} </h3>
{% block script %}
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.6/socket.io.min.js"></script>
<script type="text/javascript" src="{{ url_for('static', filename='scripts/admin.js') }}"></script>
{% endblock %}
{% endblock %}
9 changes: 9 additions & 0 deletions setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Environment variables
export DATABASE_URL="postgresql://postgres:password@localhost/cog"
export QUILL="https://hackcog.herokuapp.com"
export SECRET="8561d0aebda375a4dc5d5622250b9dc4a2387aa433d144df49d5599e8f74965e"

# Run
python initialize.py
gunicorn --worker-class eventlet -w 1 hardwarecheckout:app