From aa07aff2475a3546b44343eef017383732ac8819 Mon Sep 17 00:00:00 2001 From: Davide Alberani Date: Sat, 15 Apr 2017 11:07:27 +0200 Subject: [PATCH] fixes #165: show a warning when multiple tickets are being updated --- angular_app/js/controllers.js | 6 +- angular_app/js/directives.js | 7 ++- eventman_server.py | 30 ++++++++-- tools/qrcode_reader.ini | 17 ++++++ tools/qrcode_reader.py | 103 ++++++++++++++++++++++++++-------- 5 files changed, 133 insertions(+), 30 deletions(-) create mode 100644 tools/qrcode_reader.ini diff --git a/angular_app/js/controllers.js b/angular_app/js/controllers.js index 3e95f35..7ad8669 100644 --- a/angular_app/js/controllers.js +++ b/angular_app/js/controllers.js @@ -293,6 +293,10 @@ eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event', $log.debug('do not process our own message'); return false; } + if (data.error && data.message) { + $scope.showMessage({message: data.message, isError: true}); + return; + } if (!$scope.event.tickets) { $scope.event.tickets = []; } @@ -484,7 +488,7 @@ eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event', 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.isWarning = true; } $scope.showMessage(msg); }; diff --git a/angular_app/js/directives.js b/angular_app/js/directives.js index cee92ff..7c2e163 100644 --- a/angular_app/js/directives.js +++ b/angular_app/js/directives.js @@ -51,7 +51,12 @@ eventManApp.directive('eventmanMessage', ['$timeout', cfg = cfg || {}; scope.dControl.isVisible = true; scope.dControl.message = cfg.message; + scope.dControl.isSuccess = true; scope.dControl.isError = cfg.isError; + scope.dControl.isWarning = cfg.isWarning; + if (cfg.isError || cfg.isWarning) { + scope.dControl.isSuccess = false; + } $timeout(function () { scope.dControl.isVisible = false; }, cfg.timeout || 4000); @@ -63,7 +68,7 @@ eventManApp.directive('eventmanMessage', ['$timeout', control: '=' }, link: link, - template: '
{{dControl.message}}
' + template: '
{{dControl.message}}
' }; }] ); 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/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...')