Merge pull request #177 from alberanid/master

fixes #167: QR Code reader
This commit is contained in:
Davide Alberani 2017-04-15 16:28:58 +02:00 committed by GitHub
commit 84865a4bbb
6 changed files with 104 additions and 24 deletions

View file

@ -17,6 +17,7 @@
/* Register our fantastic app. */ /* Register our fantastic app. */
var eventManApp = angular.module('eventManApp', [ var eventManApp = angular.module('eventManApp', [
'ngRoute', 'ngRoute',
'ngAnimate',
'eventManServices', 'eventManServices',
'eventManControllers', 'eventManControllers',
'ui.bootstrap', 'ui.bootstrap',

View file

@ -293,7 +293,7 @@ eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event',
return false; return false;
} }
if (data.error && data.message) { if (data.error && data.message) {
toaster.pop({type: 'error', title: 'Error', body: data.message, timeout: 5000}); toaster.pop({type: 'error', title: 'Error', body: data.message, timeout: 0, showCloseButton: true});
return; return;
} }
if (!$scope.event.tickets) { if (!$scope.event.tickets) {
@ -508,6 +508,9 @@ eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event',
ticket_id: ticket._id ticket_id: ticket._id
}, function() { }, function() {
$scope._localRemoveTicket(ticket); $scope._localRemoveTicket(ticket);
var msg = $scope.buildTicketLabel(ticket);
msg += ' successfully removed from event ' + $scope.event.title;
toaster.pop({type: 'error', title: msg});
}); });
}; };
@ -545,7 +548,7 @@ eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event',
$log.debug('addTicket'); $log.debug('addTicket');
$log.debug(ret_ticket); $log.debug(ret_ticket);
$rootScope.$emit('event:ticket:new', ret_ticket, function() { $rootScope.$emit('event:ticket:new', ret_ticket, function() {
$rootScope.$emit('event:ticket:set-attr', ret_ticket, 'attended', true, null, false); $rootScope.$emit('event:ticket:set-attr', ret_ticket, 'attended', true, null, true);
}); });
if (cb) { if (cb) {
cb(ticket); cb(ticket);
@ -558,6 +561,9 @@ eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event',
// Close the Quick ticket modal. // Close the Quick ticket modal.
$scope.$close(); $scope.$close();
} }
var msg = $scope.buildTicketLabel(ret_ticket);
msg += ' successfully added to event ' + $scope.event.title;
toaster.pop({type: 'success', title: msg});
} }
}); });
}; };

View file

@ -793,13 +793,17 @@ class EventsHandler(CollectionHandler):
# Update an existing entry for a ticket registered at this event. # Update an existing entry for a ticket registered at this event.
self._clean_dict(data) self._clean_dict(data)
uuid, arguments = self.uuid_arguments uuid, arguments = self.uuid_arguments
_errorMessage = ''
if '_errorMessage' in arguments:
_errorMessage = arguments['_errorMessage']
del arguments['_errorMessage']
query = dict([('tickets.%s' % k, v) for k, v in arguments.items()]) query = dict([('tickets.%s' % k, v) for k, v in arguments.items()])
query['_id'] = id_ query['_id'] = id_
if ticket_id is not None: if ticket_id is not None:
query['tickets._id'] = ticket_id query['tickets._id'] = ticket_id
ticket_query = {'_id': ticket_id} ticket_query = {'_id': ticket_id}
else: else:
ticket_query = self.arguments ticket_query = arguments
old_ticket_data = {} old_ticket_data = {}
current_event = self.db.query(self.collection, query) current_event = self.db.query(self.collection, query)
if current_event: if current_event:
@ -811,15 +815,19 @@ class EventsHandler(CollectionHandler):
matching_tickets = self._get_ticket_data(ticket_query, tickets, only_one=False) matching_tickets = self._get_ticket_data(ticket_query, tickets, only_one=False)
nr_matches = len(matching_tickets) nr_matches = len(matching_tickets)
if nr_matches > 1: if nr_matches > 1:
ret = {'error': True, 'message': 'more than one ticket matched', 'query': query, ret = {'error': True, 'message': 'more than one ticket matched. %s' % _errorMessage, '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 == 0:
ret = {'error': True, 'message': 'no ticket matched. %s' % _errorMessage, 'query': query,
'uuid': uuid, 'username': self.current_user_info.get('username', '')} 'uuid': uuid, 'username': self.current_user_info.get('username', '')}
self.send_ws_message('event/%s/tickets/updates' % id_, json.dumps(ret)) self.send_ws_message('event/%s/tickets/updates' % id_, json.dumps(ret))
self.set_status(400) self.set_status(400)
return ret return ret
elif nr_matches == 1:
old_ticket_data = matching_tickets[0]
else: else:
old_ticket_data = {} old_ticket_data = matching_tickets[0]
# We have changed the "cancelled" status of a ticket to False; check if we still have a ticket available # 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'): if 'number_of_tickets' in current_event and old_ticket_data.get('cancelled') and not data.get('cancelled'):

View file

@ -103,3 +103,11 @@ input[type=text].form-control, input[type=search].form-control {
#toast-container.toast-bottom-center>div, #toast-container.toast-center>div, #toast-container.toast-top-center>div { #toast-container.toast-bottom-center>div, #toast-container.toast-center>div, #toast-container.toast-top-center>div {
margin-bottom: 4px; margin-bottom: 4px;
} }
:not(.no-enter)#toast-container > div.ng-enter {
transition-duration: .1s;
}
:not(.no-leave)#toast-container > div.ng-leave {
transition-duration: .2s;
}

View file

@ -1,3 +1,6 @@
[qrcode_reader]
debug = true
[connection] [connection]
port = /dev/ttyACM0 port = /dev/ttyACM0

View file

@ -24,8 +24,43 @@ import sys
import time import time
import serial import serial
import urllib import urllib
import logging
import argparse
import requests import requests
import configparser import configparser
from requests.packages.urllib3.exceptions import InsecureRequestWarning
logger = logging.getLogger('qrcode_reader')
logging.basicConfig(level=logging.INFO)
logging.getLogger('requests').setLevel(logging.WARNING)
logging.getLogger('urllib3').setLevel(logging.WARNING)
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
def convert_obj(obj):
try:
return int(obj)
except:
pass
if isinstance(obj, str):
obj_l = obj.lower()
if obj_l in ['true', 'on', 'yes']:
return True
elif obj_l in ['false', 'off', 'no']:
return False
return obj
def convert(seq):
if isinstance(seq, dict):
d = {}
for key, item in seq.items():
d[key] = convert(item)
return d
if isinstance(seq, (list, tuple)):
return [convert(x) for x in seq]
return convert_obj(seq)
class Connector(): class Connector():
@ -56,37 +91,46 @@ class Connector():
req.raise_for_status() req.raise_for_status()
req.connection.close() req.connection.close()
except requests.exceptions.ConnectionError as ex: except requests.exceptions.ConnectionError as ex:
print('unable to connect to %s: %s' % (self.login_url, ex)) logger.error('unable to connect to %s: %s' % (self.login_url, ex))
sys.exit(1) sys.exit(1)
def checkin(self, code): def checkin(self, code):
msg = 'scanning code %s: ' % code
limit_field = self.cfg['event'].getint('limit_field') limit_field = self.cfg['event'].getint('limit_field')
if limit_field: if limit_field:
code = code[:limit_field] code = code[:limit_field]
checkin_url = self.checkin_url + '?' + urllib.parse.urlencode({cfg['event']['field']: code}) params = {cfg['event']['field']: code, '_errorMessage': 'code: %s' % code}
params = dict(self.cfg['actions']) checkin_url = self.checkin_url + '?' + urllib.parse.urlencode(params)
req = self.session.put(checkin_url, json=params) json = convert(dict(self.cfg['actions']))
req = self.session.put(checkin_url, json=json)
error = False
try: try:
req.raise_for_status() req.raise_for_status()
msg += 'ok'
except requests.exceptions.HTTPError as ex: except requests.exceptions.HTTPError as ex:
print('error: %s' % req.json().get('message')) error = True
msg += 'error: %s' % req.json().get('message')
if not error:
logger.info(msg)
else:
logger.warning(msg)
req.connection.close() req.connection.close()
def scan(port): def scan(port):
retry = 1 retry = 1
while True: while True:
print('waiting for connection on port %s...' % port) logger.debug('waiting for connection on port %s...' % port)
try: try:
ser = serial.Serial(port=port, timeout=1) ser = serial.Serial(port=port, timeout=1)
break break
except serial.serialutil.SerialException as ex: except serial.serialutil.SerialException as ex:
if retry >= 10: if retry >= 20:
print('unable to connect: %s' % ex) logger.error('unable to connect: %s' % ex)
sys.exit(2) sys.exit(2)
time.sleep(1) time.sleep(1)
retry += 1 retry += 1
print('connected to %s' % port) logger.info('connected to %s' % port)
ser_io = io.TextIOWrapper(io.BufferedRWPair(ser, ser, 1), newline='\r', line_buffering=True) ser_io = io.TextIOWrapper(io.BufferedRWPair(ser, ser, 1), newline='\r', line_buffering=True)
while True: while True:
line = ser_io.readline().strip() line = ser_io.readline().strip()
@ -96,12 +140,22 @@ def scan(port):
if __name__ == '__main__': if __name__ == '__main__':
cfg = configparser.ConfigParser() parser = argparse.ArgumentParser()
cfg.read('qrcode_reader.ini') parser.add_argument('-c', '--code', help='specify a single code', action='store')
parser.add_argument('--config', help='user a different configuration file (default: qrcode_reader.ini)',
action='store', default='qrcode_reader.ini')
args = parser.parse_args()
cfg = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation())
cfg.read(args.config)
if cfg['qrcode_reader'].getboolean('debug'):
logging.basicConfig(level=logging.DEBUG)
connector = Connector(cfg) connector = Connector(cfg)
try: if args.code:
for code in scan(port=cfg['connection']['port']): connector.checkin(args.code)
print('received code %s' % code) else:
connector.checkin(code) try:
except KeyboardInterrupt: for code in scan(port=cfg['connection']['port']):
print('exiting...') connector.checkin(code)
except KeyboardInterrupt:
logger.info('exiting...')