diff --git a/angular_app/index.html b/angular_app/index.html
index 5396413..2791a38 100644
--- a/angular_app/index.html
+++ b/angular_app/index.html
@@ -98,6 +98,7 @@
diff --git a/angular_app/js/controllers.js b/angular_app/js/controllers.js
index 3e95f35..6a2b5ab 100644
--- a/angular_app/js/controllers.js
+++ b/angular_app/js/controllers.js
@@ -192,11 +192,10 @@ eventManControllers.controller('EventDetailsCtrl', ['$scope', '$state', 'Event',
);
-eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event', 'EventTicket', 'Setting', '$log', '$translate', '$rootScope', 'EventUpdates', '$uibModal', '$filter',
- function ($scope, $state, Event, EventTicket, Setting, $log, $translate, $rootScope, EventUpdates, $uibModal, $filter) {
+eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event', 'EventTicket', 'Setting', '$log', '$translate', '$rootScope', 'EventUpdates', '$uibModal', '$filter', 'toaster',
+ function ($scope, $state, Event, EventTicket, Setting, $log, $translate, $rootScope, EventUpdates, $uibModal, $filter, toaster) {
$scope.ticketsOrder = ["name", "surname"];
$scope.countAttendees = 0;
- $scope.message = {};
$scope.query = '';
$scope.event = {};
$scope.event.tickets = [];
@@ -293,6 +292,10 @@ eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event',
$log.debug('do not process our own message');
return false;
}
+ if (data.error && data.message) {
+ toaster.pop({type: 'error', title: 'Error', body: data.message, timeout: 5000});
+ return;
+ }
if (!$scope.event.tickets) {
$scope.event.tickets = [];
}
@@ -478,15 +481,16 @@ eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event',
$scope.showAttendedMessage = function(ticket, attends) {
var msg = {};
+ var msg_type = 'success';
var name = $scope.buildTicketLabel(ticket);
if (attends) {
msg.message = name + ' successfully added to event ' + $scope.event.title;
} else {
msg.message = name + ' successfully removed from event ' + $scope.event.title;
- msg.isError = true;
+ msg_type = 'warning';
}
- $scope.showMessage(msg);
+ toaster.pop({type: msg_type, title: msg.message});
};
$scope.setTicketAttributeAndRefocus = function(ticket, key, value) {
@@ -593,7 +597,7 @@ eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event',
});
if ($state.is('event.ticket.edit')) {
$scope.updateTicket($scope.ticket, function() {
- $scope.showMessage({message: 'ticket successfully updated'});
+ toaster.pop({type: 'info', title: 'ticket successfully updated'});
});
} else {
$scope.addTicket($scope.ticket);
@@ -647,10 +651,6 @@ eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event',
$scope.filterTickets();
};
- $scope.showMessage = function(cfg) {
- $scope.message && $scope.message.show && $scope.message.show(cfg);
- };
-
$scope.$on('$destroy', function() {
$scope.EventUpdates && $scope.EventUpdates.close();
});
diff --git a/angular_app/js/directives.js b/angular_app/js/directives.js
index cee92ff..772ba98 100644
--- a/angular_app/js/directives.js
+++ b/angular_app/js/directives.js
@@ -40,31 +40,3 @@ eventManApp.directive('resetFocus', function () {
};
});
-
-eventManApp.directive('eventmanMessage', ['$timeout',
- function($timeout) {
- function link(scope, element, attrs) {
- scope.dControl = scope.control || {};
- scope.dControl.isVisible = false;
-
- scope.dControl.show = function(cfg) {
- cfg = cfg || {};
- scope.dControl.isVisible = true;
- scope.dControl.message = cfg.message;
- scope.dControl.isError = cfg.isError;
- $timeout(function () {
- scope.dControl.isVisible = false;
- }, cfg.timeout || 4000);
- };
- };
-
- return {
- scope: {
- control: '='
- },
- link: link,
- template: '
{{dControl.message}}
'
- };
- }]
-);
-
diff --git a/angular_app/ticket-edit.html b/angular_app/ticket-edit.html
index 4355807..1f581bd 100644
--- a/angular_app/ticket-edit.html
+++ b/angular_app/ticket-edit.html
@@ -1,7 +1,5 @@
-
-
diff --git a/eventman_server.py b/eventman_server.py
index d6ecd4c..9b4d536 100755
--- a/eventman_server.py
+++ b/eventman_server.py
@@ -668,17 +668,26 @@ class EventsHandler(CollectionHandler):
persons += [p for p in (event.get('tickets') or []) if p.get('email') and p.get('email') not in this_emails]
return {'persons': persons}
- def _get_ticket_data(self, ticket_id_or_query, tickets):
+ def _get_ticket_data(self, ticket_id_or_query, tickets, only_one=True):
"""Filter a list of tickets returning the first item with a given _id
or which set of keys specified in a dictionary match their respective values."""
+ matches = []
for ticket in tickets:
if isinstance(ticket_id_or_query, dict):
if all(ticket.get(k) == v for k, v in ticket_id_or_query.items()):
- return ticket
+ matches.append(ticket)
+ if only_one:
+ break
else:
if str(ticket.get('_id')) == ticket_id_or_query:
- return ticket
- return {}
+ matches.append(ticket)
+ if only_one:
+ break
+ if only_one:
+ if matches:
+ return matches[0]
+ return {}
+ return matches
def handle_get_tickets(self, id_, resource_id=None):
# Return every ticket registered at this event, or the information
@@ -799,7 +808,18 @@ class EventsHandler(CollectionHandler):
current_event = {}
self._check_sales_datetime(current_event)
tickets = current_event.get('tickets') or []
- old_ticket_data = self._get_ticket_data(ticket_query, tickets)
+ matching_tickets = self._get_ticket_data(ticket_query, tickets, only_one=False)
+ nr_matches = len(matching_tickets)
+ if nr_matches > 1:
+ ret = {'error': True, 'message': 'more than one ticket matched', 'query': query,
+ 'uuid': uuid, 'username': self.current_user_info.get('username', '')}
+ self.send_ws_message('event/%s/tickets/updates' % id_, json.dumps(ret))
+ self.set_status(400)
+ return ret
+ elif nr_matches == 1:
+ old_ticket_data = matching_tickets[0]
+ else:
+ old_ticket_data = {}
# We have changed the "cancelled" status of a ticket to False; check if we still have a ticket available
if 'number_of_tickets' in current_event and old_ticket_data.get('cancelled') and not data.get('cancelled'):
diff --git a/static/css/eventman.css b/static/css/eventman.css
index d69a972..2438f85 100644
--- a/static/css/eventman.css
+++ b/static/css/eventman.css
@@ -99,3 +99,7 @@ input[type=text].form-control, input[type=search].form-control {
.registered-counter {
margin-right: 5px;
}
+
+#toast-container.toast-bottom-center>div, #toast-container.toast-center>div, #toast-container.toast-top-center>div {
+ margin-bottom: 4px;
+}
diff --git a/tools/qrcode_reader.ini b/tools/qrcode_reader.ini
new file mode 100644
index 0000000..bca8f0f
--- /dev/null
+++ b/tools/qrcode_reader.ini
@@ -0,0 +1,17 @@
+[connection]
+port = /dev/ttyACM0
+
+[eventman]
+url = https://localhost:5242/
+username = admin
+password = eventman
+ca =
+
+[event]
+id = 1492099112_2612922-3896-9zwsccuvguz91jtw9y6lwvkud11ba7wt
+field = order_nr
+limit_field = 9
+
+[actions]
+attended = True
+checked_in_by = ${eventman:username}
diff --git a/tools/qrcode_reader.py b/tools/qrcode_reader.py
index 7d8703c..b091d5a 100755
--- a/tools/qrcode_reader.py
+++ b/tools/qrcode_reader.py
@@ -1,34 +1,92 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
+"""qrcode_reader
+Scan the output of a serial QR Code reader.
+
+Copyright 2017 Davide Alberani
+ RaspiBO
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+import os
import io
+import sys
+import time
import serial
+import urllib
import requests
+import configparser
class Connector():
- def __init__(self, login_url, checkin_url, username=None, password=None):
- self.login_url = login_url
- self.checkin_url = checkin_url
- self.session = requests.Session()
- json = {}
- if username:
- json['username'] = username
- if password:
- json['password'] = password
- req = self.session.post(login_url, json=json, verify=False)
- req.raise_for_status()
- req.connection.close()
+ def __init__(self, cfg):
+ self.cfg = cfg
+ self.session = None
+ self.url = cfg['eventman']['url']
+ self.login_url = urllib.parse.urljoin(self.url, '/v1.0/login')
+ self.checkin_url = urllib.parse.urljoin(self.url, os.path.join('/v1.0/events/',
+ cfg['event']['id'], 'tickets/'))
+ self.login()
+
+ def login(self):
+ try:
+ self.session = requests.Session()
+ self.session.verify = False
+ ca = cfg['eventman'].get('ca')
+ if ca and os.path.isfile(ca):
+ self.session.verify = ca
+ username = cfg['eventman'].get('username')
+ password = cfg['eventman'].get('password')
+ params = {}
+ if username:
+ params['username'] = username
+ if password:
+ params['password'] = password
+ req = self.session.post(self.login_url, json=params)
+ req.raise_for_status()
+ req.connection.close()
+ except requests.exceptions.ConnectionError as ex:
+ print('unable to connect to %s: %s' % (self.login_url, ex))
+ sys.exit(1)
def checkin(self, code):
- req = self.session.put(self.checkin_url + '?order_nr=' + code[:9], json={'attended': True}, verify=False)
- req.raise_for_status()
+ limit_field = self.cfg['event'].getint('limit_field')
+ if limit_field:
+ code = code[:limit_field]
+ checkin_url = self.checkin_url + '?' + urllib.parse.urlencode({cfg['event']['field']: code})
+ params = dict(self.cfg['actions'])
+ req = self.session.put(checkin_url, json=params)
+ try:
+ req.raise_for_status()
+ except requests.exceptions.HTTPError as ex:
+ print('error: %s' % req.json().get('message'))
req.connection.close()
-
-def scan():
- ser = serial.Serial(port='/dev/ttyACM0', timeout=1)
+def scan(port):
+ retry = 1
+ while True:
+ print('waiting for connection on port %s...' % port)
+ try:
+ ser = serial.Serial(port=port, timeout=1)
+ break
+ except serial.serialutil.SerialException as ex:
+ if retry >= 10:
+ print('unable to connect: %s' % ex)
+ sys.exit(2)
+ time.sleep(1)
+ retry += 1
+ print('connected to %s' % port)
ser_io = io.TextIOWrapper(io.BufferedRWPair(ser, ser, 1), newline='\r', line_buffering=True)
while True:
line = ser_io.readline().strip()
@@ -38,13 +96,12 @@ def scan():
if __name__ == '__main__':
- connector = Connector(login_url='https://localhost:5242/v1.0/login',
- checkin_url='https://localhost:5242/v1.0/events/1490640884_8820477-7-7gvft6nlrs2o73fza54a6yeywiowmj8v/tickets/',
- username='admin',
- password='eventman')
+ cfg = configparser.ConfigParser()
+ cfg.read('qrcode_reader.ini')
+ connector = Connector(cfg)
try:
- for code in scan():
- print(code)
+ for code in scan(port=cfg['connection']['port']):
+ print('received code %s' % code)
connector.checkin(code)
except KeyboardInterrupt:
print('exiting...')