diff --git a/angular_app/event-tickets.html b/angular_app/event-tickets.html index ae0a47e..7da52e3 100644 --- a/angular_app/event-tickets.html +++ b/angular_app/event-tickets.html @@ -1,6 +1,5 @@
-
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...')