Merge pull request #176 from alberanid/master
toaster and basic qr code scanner
This commit is contained in:
commit
e8e10ced5d
9 changed files with 137 additions and 69 deletions
|
@ -1,6 +1,5 @@
|
|||
<!-- show details of an Event -->
|
||||
<div class="container">
|
||||
<div eventman-message="eventman-message" control="message"></div>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
|
|
|
@ -98,6 +98,7 @@
|
|||
|
||||
<div class="main-header">
|
||||
</div>
|
||||
<toaster-container toaster-options="{'time-out': 4000, 'position-class': 'toast-top-center'}"></toaster-container>
|
||||
|
||||
<!-- main error handling -->
|
||||
<div ng-if="error.error" ng-class="{clearfix: true, alert: true, 'alert-danger': true}">
|
||||
|
|
20
angular_app/js/controllers.js
vendored
20
angular_app/js/controllers.js
vendored
|
@ -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();
|
||||
});
|
||||
|
|
28
angular_app/js/directives.js
vendored
28
angular_app/js/directives.js
vendored
|
@ -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: '<div ng-if="dControl.isVisible" ng-class="{\'eventman-message\': true, clearfix: true, \'alert\': true, \'alert-success\': !dControl.isError, \'alert-danger\': dControl.isError}">{{dControl.message}}</div>'
|
||||
};
|
||||
}]
|
||||
);
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<!-- show details of an Event -->
|
||||
<div class="container">
|
||||
<div eventman-message="eventman-message" control="message"></div>
|
||||
|
||||
<!-- FIXME: ideally, here we would have put a ng-if="!ticket.cancelled" directive, but for some
|
||||
odd reason, any kind ng-if directive will prevent the form being populated with the formData model.
|
||||
-->
|
||||
|
|
|
@ -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'):
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
17
tools/qrcode_reader.ini
Normal file
17
tools/qrcode_reader.ini
Normal file
|
@ -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}
|
|
@ -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 <da@erlug.linux.it>
|
||||
RaspiBO <info@raspibo.org>
|
||||
|
||||
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...')
|
||||
|
|
Loading…
Reference in a new issue