Merge pull request #170 from alberanid/master
getting ready for hib 2017 spring
This commit is contained in:
commit
e25c9ae385
18 changed files with 433 additions and 195 deletions
15
README.md
15
README.md
|
@ -31,6 +31,7 @@ See the *docs/DEVELOPMENT.md* file for more information about how to contribute.
|
||||||
Technological stack
|
Technological stack
|
||||||
===================
|
===================
|
||||||
|
|
||||||
|
- [Python 3](https://www.python.org/) for the backend
|
||||||
- [AngularJS](https://angularjs.org/) (plus some third-party modules) for the webApp
|
- [AngularJS](https://angularjs.org/) (plus some third-party modules) for the webApp
|
||||||
- [Angular Easy form Generator](https://mackentoch.github.io/easyFormGenerator/) for the custom forms
|
- [Angular Easy form Generator](https://mackentoch.github.io/easyFormGenerator/) for the custom forms
|
||||||
- [Bootstrap](http://getbootstrap.com/) (plus [Angular UI](https://angular-ui.github.io/bootstrap/)) for the eye-candy
|
- [Bootstrap](http://getbootstrap.com/) (plus [Angular UI](https://angular-ui.github.io/bootstrap/)) for the eye-candy
|
||||||
|
@ -45,14 +46,14 @@ If you want to print labels using the _print\_label_ trigger, you may also need
|
||||||
Install and run
|
Install and run
|
||||||
===============
|
===============
|
||||||
|
|
||||||
Be sure to have a running MongoDB server, locally. If you want to install the dependencies only locally to the current user, you can append the *--user* argument to the *pip* calls. Please also install the *python-dev* package, before running the following commands.
|
Be sure to have a running MongoDB server, locally. If you want to install the dependencies only locally to the current user, you can append the *--user* argument to the *pip* calls. Please also install the *python3-dev* package, before running the following commands.
|
||||||
|
|
||||||
wget https://bootstrap.pypa.io/get-pip.py
|
wget https://bootstrap.pypa.io/get-pip.py
|
||||||
sudo python get-pip.py
|
sudo python3 get-pip.py
|
||||||
sudo pip install tornado # version 4.2 or later
|
sudo pip3 install tornado # version 4.2 or later
|
||||||
sudo pip install pymongo # version 3.2.2 or later
|
sudo pip3 install pymongo # version 3.2.2 or later
|
||||||
sudo pip install python-dateutil
|
sudo pip3 install python-dateutil
|
||||||
sudo pip install pycups # only needed if you want to print labels
|
sudo pip3 install pycups # only needed if you want to print labels
|
||||||
git clone https://github.com/raspibo/eventman
|
git clone https://github.com/raspibo/eventman
|
||||||
cd eventman
|
cd eventman
|
||||||
./eventman_server.py --debug
|
./eventman_server.py --debug
|
||||||
|
@ -114,7 +115,7 @@ Users can register, but are not forced to do so: tickets can also be issued to u
|
||||||
License and copyright
|
License and copyright
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
Copyright 2015-2016 Davide Alberani <da@erlug.linux.it>, RaspiBO <info@raspibo.org>
|
Copyright 2015-2017 Davide Alberani <da@erlug.linux.it>, RaspiBO <info@raspibo.org>
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -27,9 +27,12 @@
|
||||||
<form class="form-inline">
|
<form class="form-inline">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="query-tickets">{{'Search:' | translate}}</label>
|
<label for="query-tickets">{{'Search:' | translate}}</label>
|
||||||
<input eventman-focus type="text" id="query-tickets" class="form-control" placeholder="{{'Name or email' | translate}}" ng-model="query" ng-model-options="{debounce: 600}">
|
<input eventman-focus type="text" id="query-tickets" class="form-control" placeholder="{{'Name or email' | translate}}" ng-model="query" ng-model-options="{debounce: 350}">
|
||||||
</div> <label> <input type="checkbox" ng-model="registeredFilterOptions.all" /> {{'Show cancelled tickets' | translate}}</label>
|
</div> <label> <input type="checkbox" ng-model="registeredFilterOptions.all" /> {{'Show cancelled tickets' | translate}}</label>
|
||||||
</form>
|
</form>
|
||||||
|
<pagination ng-model="currentPage" total-items="filteredLength" items-per-page="itemsPerPage"
|
||||||
|
direction-links="false" boundary-links="true" boundary-link-numbers="true" max-size="maxPaginationSize">
|
||||||
|
</pagination>
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -43,7 +46,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ng-repeat="ticket in (event.tickets || []) | splittedFilter:query | registeredFilter:registeredFilterOptions | orderBy:ticketsOrder">
|
<tr ng-repeat="ticket in shownItems">
|
||||||
<td class="text-right">{{$index+1}}</td>
|
<td class="text-right">{{$index+1}}</td>
|
||||||
<td>
|
<td>
|
||||||
<span>
|
<span>
|
||||||
|
@ -71,6 +74,9 @@
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<pagination ng-model="currentPage" total-items="filteredLength" items-per-page="itemsPerPage"
|
||||||
|
direction-links="false" boundary-links="true" boundary-link-numbers="true" max-size="maxPaginationSize">
|
||||||
|
</pagination>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -87,7 +93,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ng-repeat="person in allPersons | splittedFilter:query | personRegistered:{event: event, present: false}">
|
<tr ng-repeat="person in (query ? allPersons : []) | splittedFilter:query | personRegistered:{event: event, present: false} | limitTo:maxAllPersons">
|
||||||
<td>
|
<td>
|
||||||
<strong>{{person.name}} {{person.surname}}</strong>
|
<strong>{{person.name}} {{person.surname}}</strong>
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
<form class="form-inline">
|
<form class="form-inline">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="query-events">{{'Search:' | translate}}</label>
|
<label for="query-events">{{'Search:' | translate}}</label>
|
||||||
<input eventman-focus type="text" id="query-events" class="form-control" placeholder="{{'Event title' | translate}}" ng-model="query" ng-model-options="{debounce: 600}">
|
<input eventman-focus type="text" id="query-events" class="form-control" placeholder="{{'Event title' | translate}}" ng-model="query" ng-model-options="{debounce: 350}">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="events-order">Sort by:</label>
|
<label for="events-order">Sort by:</label>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<script type="text/javascript" src="/static/js/angular-resource.min.js"></script>
|
<script type="text/javascript" src="/static/js/angular-resource.min.js"></script>
|
||||||
<script type="text/javascript" src="/static/js/angular-file-upload.min.js"></script>
|
<script type="text/javascript" src="/static/js/angular-file-upload.min.js"></script>
|
||||||
<script type="text/javascript" src="/static/js/angular-ui-router.min.js"></script>
|
<script type="text/javascript" src="/static/js/angular-ui-router.min.js"></script>
|
||||||
<script type="text/javascript" src="/static/js/angular-websocket.js"></script>
|
<script type="text/javascript" src="/static/js/angular-websocket.min.js"></script>
|
||||||
<script type="text/javascript" src="/static/js/angular-translate.min.js"></script>
|
<script type="text/javascript" src="/static/js/angular-translate.min.js"></script>
|
||||||
<script type="text/javascript" src="/static/js/angular-translate-loader-static-files.min.js"></script>
|
<script type="text/javascript" src="/static/js/angular-translate-loader-static-files.min.js"></script>
|
||||||
<script type="text/javascript" src="/static/js/nya-bs-select.min.js"></script>
|
<script type="text/javascript" src="/static/js/nya-bs-select.min.js"></script>
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Copyright 2015-2016 Davide Alberani <da@erlug.linux.it>
|
Copyright 2015-2017 Davide Alberani <da@erlug.linux.it>
|
||||||
RaspiBO <info@raspibo.org>
|
RaspiBO <info@raspibo.org>
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|
2
angular_app/js/app.js
vendored
2
angular_app/js/app.js
vendored
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
/*
|
/*
|
||||||
Copyright 2015-2016 Davide Alberani <da@erlug.linux.it>
|
Copyright 2015-2017 Davide Alberani <da@erlug.linux.it>
|
||||||
RaspiBO <info@raspibo.org>
|
RaspiBO <info@raspibo.org>
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|
106
angular_app/js/controllers.js
vendored
106
angular_app/js/controllers.js
vendored
|
@ -66,8 +66,9 @@ eventManControllers.controller('ModalConfirmInstanceCtrl', ['$scope', '$uibModal
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
eventManControllers.controller('EventsListCtrl', ['$scope', 'Event', '$uibModal', '$log', '$translate', '$rootScope', '$state',
|
eventManControllers.controller('EventsListCtrl', ['$scope', 'Event', '$uibModal', '$log', '$translate', '$rootScope', '$state', '$filter',
|
||||||
function ($scope, Event, $uibModal, $log, $translate, $rootScope, $state) {
|
function ($scope, Event, $uibModal, $log, $translate, $rootScope, $state, $filter) {
|
||||||
|
$scope.query = '';
|
||||||
$scope.tickets = [];
|
$scope.tickets = [];
|
||||||
$scope.events = Event.all(function(events) {
|
$scope.events = Event.all(function(events) {
|
||||||
if (events && $state.is('tickets')) {
|
if (events && $state.is('tickets')) {
|
||||||
|
@ -79,11 +80,38 @@ eventManControllers.controller('EventsListCtrl', ['$scope', 'Event', '$uibModal'
|
||||||
});
|
});
|
||||||
$scope.tickets.push.apply($scope.tickets, evt_tickets || []);
|
$scope.tickets.push.apply($scope.tickets, evt_tickets || []);
|
||||||
});
|
});
|
||||||
|
$scope.filterTickets();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$scope.eventsOrderProp = "-begin_date";
|
$scope.eventsOrderProp = "-begin_date";
|
||||||
$scope.ticketsOrderProp = ["name", "surname"];
|
$scope.ticketsOrderProp = ["name", "surname"];
|
||||||
|
|
||||||
|
$scope.shownItems = [];
|
||||||
|
$scope.currentPage = 1;
|
||||||
|
$scope.itemsPerPage = 10;
|
||||||
|
$scope.filteredLength = 0;
|
||||||
|
$scope.maxPaginationSize = 10;
|
||||||
|
|
||||||
|
$scope.filterTickets = function() {
|
||||||
|
var tickets = $scope.tickets || [];
|
||||||
|
tickets = $filter('splittedFilter')(tickets, $scope.query);
|
||||||
|
tickets = $filter('orderBy')(tickets, $scope.ticketsOrderProp);
|
||||||
|
$scope.filteredLength = tickets.length;
|
||||||
|
tickets = $filter('pagination')(tickets, $scope.currentPage, $scope.itemsPerPage);
|
||||||
|
$scope.shownItems = tickets;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.$watch('query', function() {
|
||||||
|
if (!$scope.query) {
|
||||||
|
$scope.currentPage = 1;
|
||||||
|
}
|
||||||
|
$scope.filterTickets();
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.$watch('currentPage + itemsPerPage', function() {
|
||||||
|
$scope.filterTickets();
|
||||||
|
});
|
||||||
|
|
||||||
$scope.confirm_delete = 'Do you really want to delete this event?';
|
$scope.confirm_delete = 'Do you really want to delete this event?';
|
||||||
$rootScope.$on('$translateChangeSuccess', function () {
|
$rootScope.$on('$translateChangeSuccess', function () {
|
||||||
$translate('Do you really want to delete this event?').then(function (translation) {
|
$translate('Do you really want to delete this event?').then(function (translation) {
|
||||||
|
@ -123,8 +151,8 @@ eventManControllers.controller('EventsListCtrl', ['$scope', 'Event', '$uibModal'
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
$scope.ticketsOrderProp = new_order;
|
$scope.ticketsOrderProp = new_order;
|
||||||
|
$scope.filterTickets();
|
||||||
};
|
};
|
||||||
|
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -166,13 +194,15 @@ eventManControllers.controller('EventDetailsCtrl', ['$scope', '$state', 'Event',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event', 'EventTicket', 'Setting', '$log', '$translate', '$rootScope', 'EventUpdates', '$uibModal',
|
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) {
|
function ($scope, $state, Event, EventTicket, Setting, $log, $translate, $rootScope, EventUpdates, $uibModal, $filter) {
|
||||||
$scope.ticketsOrder = ["name", "surname"];
|
$scope.ticketsOrder = ["name", "surname"];
|
||||||
$scope.countAttendees = 0;
|
$scope.countAttendees = 0;
|
||||||
$scope.message = {};
|
$scope.message = {};
|
||||||
|
$scope.query = '';
|
||||||
$scope.event = {};
|
$scope.event = {};
|
||||||
$scope.event.tickets = [];
|
$scope.event.tickets = [];
|
||||||
|
$scope.shownItems = [];
|
||||||
$scope.ticket = {}; // current ticket, for the event.ticket.* states
|
$scope.ticket = {}; // current ticket, for the event.ticket.* states
|
||||||
$scope.tickets = []; // list of all tickets, for the 'tickets' state
|
$scope.tickets = []; // list of all tickets, for the 'tickets' state
|
||||||
$scope.formSchema = {};
|
$scope.formSchema = {};
|
||||||
|
@ -180,16 +210,47 @@ eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event',
|
||||||
$scope.guiOptions = {dangerousActionsEnabled: false};
|
$scope.guiOptions = {dangerousActionsEnabled: false};
|
||||||
$scope.customFields = Setting.query({setting: 'ticket_custom_field', in_event_details: true});
|
$scope.customFields = Setting.query({setting: 'ticket_custom_field', in_event_details: true});
|
||||||
$scope.registeredFilterOptions = {all: false};
|
$scope.registeredFilterOptions = {all: false};
|
||||||
|
|
||||||
$scope.formFieldsMap = {};
|
$scope.formFieldsMap = {};
|
||||||
$scope.formFieldsMapRev = {};
|
$scope.formFieldsMapRev = {};
|
||||||
|
|
||||||
|
$scope.currentPage = 1;
|
||||||
|
$scope.itemsPerPage = 10;
|
||||||
|
$scope.filteredLength = 0;
|
||||||
|
$scope.maxPaginationSize = 10;
|
||||||
|
$scope.maxAllPersons = 10;
|
||||||
|
|
||||||
|
$scope.filterTickets = function() {
|
||||||
|
var tickets = $scope.event.tickets || [];
|
||||||
|
tickets = $filter('splittedFilter')(tickets, $scope.query);
|
||||||
|
tickets = $filter('registeredFilter')(tickets, $scope.registeredFilterOptions);
|
||||||
|
tickets = $filter('orderBy')(tickets, $scope.ticketsOrder);
|
||||||
|
$scope.filteredLength = tickets.length;
|
||||||
|
tickets = $filter('pagination')(tickets, $scope.currentPage, $scope.itemsPerPage);
|
||||||
|
$scope.shownItems = tickets;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.$watch('query', function() {
|
||||||
|
if (!$scope.query) {
|
||||||
|
$scope.currentPage = 1;
|
||||||
|
}
|
||||||
|
$scope.filterTickets();
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.$watchCollection('registeredFilterOptions', function() {
|
||||||
|
$scope.filterTickets();
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.$watch('currentPage + itemsPerPage', function() {
|
||||||
|
$scope.filterTickets();
|
||||||
|
});
|
||||||
|
|
||||||
if ($state.params.id) {
|
if ($state.params.id) {
|
||||||
$scope.event = Event.get({id: $state.params.id}, function(data) {
|
$scope.event = Event.get({id: $state.params.id}, function(data) {
|
||||||
$scope.$watchCollection(function() {
|
$scope.$watchCollection(function() {
|
||||||
return $scope.event.tickets;
|
return $scope.event.tickets;
|
||||||
}, function(new_collection, old_collection) {
|
}, function(new_collection, old_collection) {
|
||||||
$scope.calcAttendees();
|
$scope.calcAttendees();
|
||||||
|
$scope.filterTickets();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -248,6 +309,12 @@ eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event',
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.action == 'update' && ticket_idx != -1 && $scope.event.tickets[ticket_idx] != data.ticket) {
|
if (data.action == 'update' && ticket_idx != -1 && $scope.event.tickets[ticket_idx] != data.ticket) {
|
||||||
|
// if we're updating the 'attended' key and the action came from us (same user, possibly on
|
||||||
|
// a different station), also show a message.
|
||||||
|
if (data.ticket.attended != $scope.event.tickets[ticket_idx].attended &&
|
||||||
|
$scope.info.user.username == data.username) {
|
||||||
|
$scope.showAttendedMessage(data.ticket, data.ticket.attended);
|
||||||
|
}
|
||||||
$scope.event.tickets[ticket_idx] = data.ticket;
|
$scope.event.tickets[ticket_idx] = data.ticket;
|
||||||
} else if (data.action == 'add' && ticket_idx == -1) {
|
} else if (data.action == 'add' && ticket_idx == -1) {
|
||||||
$scope._localAddTicket(data.ticket);
|
$scope._localAddTicket(data.ticket);
|
||||||
|
@ -406,20 +473,24 @@ eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event',
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === 'attended' && !hideMessage) {
|
if (key === 'attended' && !hideMessage) {
|
||||||
var msg = {};
|
$scope.showAttendedMessage(data.ticket, value);
|
||||||
var name = $scope.buildTicketLabel(data.ticket);
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
msg.message = name + ' successfully added to event ' + $scope.event.title;
|
|
||||||
} else {
|
|
||||||
msg.message = name + ' successfully removed from event ' + $scope.event.title;
|
|
||||||
msg.isError = true;
|
|
||||||
}
|
|
||||||
$scope.showMessage(msg);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.showAttendedMessage = function(ticket, attends) {
|
||||||
|
var msg = {};
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
$scope.showMessage(msg);
|
||||||
|
};
|
||||||
|
|
||||||
$scope.setTicketAttributeAndRefocus = function(ticket, key, value) {
|
$scope.setTicketAttributeAndRefocus = function(ticket, key, value) {
|
||||||
$scope.setTicketAttribute(ticket, key, value);
|
$scope.setTicketAttribute(ticket, key, value);
|
||||||
$scope.query = '';
|
$scope.query = '';
|
||||||
|
@ -575,6 +646,7 @@ eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
$scope.ticketsOrder = new_order;
|
$scope.ticketsOrder = new_order;
|
||||||
|
$scope.filterTickets();
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.showMessage = function(cfg) {
|
$scope.showMessage = function(cfg) {
|
||||||
|
@ -649,7 +721,7 @@ eventManControllers.controller('UsersCtrl', ['$scope', '$rootScope', '$state', '
|
||||||
User.login(loginData, function(data) {
|
User.login(loginData, function(data) {
|
||||||
if (!data.error) {
|
if (!data.error) {
|
||||||
$rootScope.readInfo(function(info) {
|
$rootScope.readInfo(function(info) {
|
||||||
$log.debug('logged in user: ' + info.user.username);
|
$log.debug('logged in user: ' + $scope.info.user.username);
|
||||||
$rootScope.clearError();
|
$rootScope.clearError();
|
||||||
$state.go('events');
|
$state.go('events');
|
||||||
});
|
});
|
||||||
|
|
12
angular_app/js/filters.js
vendored
12
angular_app/js/filters.js
vendored
|
@ -68,6 +68,18 @@ eventManApp.filter('registeredFilter', ['$filter',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/* Filter that implements a generic pagination. */
|
||||||
|
eventManApp.filter('pagination', ['$filter',
|
||||||
|
function($filter) {
|
||||||
|
return function(inputArray, page, itemsPerPage) {
|
||||||
|
var begin = (page - 1) * itemsPerPage;
|
||||||
|
var end = begin + itemsPerPage;
|
||||||
|
return inputArray.slice(begin, end);;
|
||||||
|
};
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
/* Filter that returns only the attendees at an event. */
|
/* Filter that returns only the attendees at an event. */
|
||||||
eventManApp.filter('attendeesFilter', ['$filter',
|
eventManApp.filter('attendeesFilter', ['$filter',
|
||||||
function($filter) {
|
function($filter) {
|
||||||
|
|
|
@ -11,10 +11,13 @@
|
||||||
<form class="form-inline">
|
<form class="form-inline">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="query-tickets">{{'Search:' | translate}}</label>
|
<label for="query-tickets">{{'Search:' | translate}}</label>
|
||||||
<input eventman-focus type="text" id="query-tickets" class="form-control" placeholder="{{'Name or email' | translate}}" ng-model="query" ng-model-options="{debounce: 600}">
|
<input eventman-focus type="text" id="query-tickets" class="form-control" placeholder="{{'Name or email' | translate}}" ng-model="query" ng-model-options="{debounce: 350}">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<pagination ng-model="currentPage" total-items="filteredLength" items-per-page="itemsPerPage"
|
||||||
|
direction-links="false" boundary-links="true" boundary-link-numbers="true" max-size="maxPaginationSize">
|
||||||
|
</pagination>
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -25,7 +28,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ng-repeat="ticket in tickets | splittedFilter:query | orderBy:ticketsOrderProp">
|
<tr ng-repeat="ticket in shownItems">
|
||||||
<td class="text-right">{{$index+1}}</td>
|
<td class="text-right">{{$index+1}}</td>
|
||||||
<td>
|
<td>
|
||||||
<span><strong><a ui-sref="event.ticket.edit({id: ticket.event_id, ticket_id: ticket._id})"><span>{{ticket.name}}</span> <span>{{ticket.surname}}</span></a></strong></span><span ng-if="ticket.email"> <{{ticket.email}}></span>
|
<span><strong><a ui-sref="event.ticket.edit({id: ticket.event_id, ticket_id: ticket._id})"><span>{{ticket.name}}</span> <span>{{ticket.surname}}</span></a></strong></span><span ng-if="ticket.email"> <{{ticket.email}}></span>
|
||||||
|
@ -40,6 +43,9 @@
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<pagination ng-model="currentPage" total-items="filteredLength" items-per-page="itemsPerPage"
|
||||||
|
direction-links="false" boundary-links="true" boundary-link-numbers="true" max-size="maxPaginationSize">
|
||||||
|
</pagination>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
<form class="form-inline">
|
<form class="form-inline">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="query-tickets">{{'Search:' | translate}}</label>
|
<label for="query-tickets">{{'Search:' | translate}}</label>
|
||||||
<input eventman-focus type="text" id="query-tickets" class="form-control" placeholder="{{'Event title' | translate}}" ng-model="query" ng-model-options="{debounce: 600}">
|
<input eventman-focus type="text" id="query-tickets" class="form-control" placeholder="{{'Event title' | translate}}" ng-model="query" ng-model-options="{debounce: 350}">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="tickets-order">{{'Sort by:' | translate}}</label>
|
<label for="tickets-order">{{'Sort by:' | translate}}</label>
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<form class="form-inline">
|
<form class="form-inline">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="query-users">{{'Search:' | translate}}</label>
|
<label for="query-users">{{'Search:' | translate}}</label>
|
||||||
<input userman-focus type="text" id="query-users" class="form-control" placeholder="{{'Username or email' | translate}}" ng-model="query" ng-model-options="{debounce: 600}">
|
<input userman-focus type="text" id="query-users" class="form-control" placeholder="{{'Username or email' | translate}}" ng-model="query" ng-model-options="{debounce: 350}">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="users-order">Sort by:</label>
|
<label for="users-order">Sort by:</label>
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
Development
|
Development
|
||||||
===========
|
===========
|
||||||
|
|
||||||
As of July 2016, EventMan(ager) is under heavy refactoring. For a list of main changes that will be introduced, see https://github.com/raspibo/eventman/issues
|
|
||||||
|
|
||||||
Every contribution, in form of code or ideas, is welcome.
|
Every contribution, in form of code or ideas, is welcome.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
"""EventMan(ager)
|
"""EventMan(ager)
|
||||||
|
|
||||||
Your friendly manager of attendees at an event.
|
Your friendly manager of attendees at an event.
|
||||||
|
|
||||||
Copyright 2015-2016 Davide Alberani <da@erlug.linux.it>
|
Copyright 2015-2017 Davide Alberani <da@erlug.linux.it>
|
||||||
RaspiBO <info@raspibo.org>
|
RaspiBO <info@raspibo.org>
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -38,7 +38,8 @@ import tornado.websocket
|
||||||
from tornado import gen, escape, process
|
from tornado import gen, escape, process
|
||||||
|
|
||||||
import utils
|
import utils
|
||||||
import backend
|
import monco
|
||||||
|
import collections
|
||||||
|
|
||||||
ENCODING = 'utf-8'
|
ENCODING = 'utf-8'
|
||||||
PROCESS_TIMEOUT = 60
|
PROCESS_TIMEOUT = 60
|
||||||
|
@ -102,8 +103,8 @@ class BaseHandler(tornado.web.RequestHandler):
|
||||||
_users_cache = {}
|
_users_cache = {}
|
||||||
|
|
||||||
# A property to access the first value of each argument.
|
# A property to access the first value of each argument.
|
||||||
arguments = property(lambda self: dict([(k, v[0])
|
arguments = property(lambda self: dict([(k, v[0].decode('utf-8'))
|
||||||
for k, v in self.request.arguments.iteritems()]))
|
for k, v in self.request.arguments.items()]))
|
||||||
|
|
||||||
# A property to access both the UUID and the clean arguments.
|
# A property to access both the UUID and the clean arguments.
|
||||||
@property
|
@property
|
||||||
|
@ -150,23 +151,26 @@ class BaseHandler(tornado.web.RequestHandler):
|
||||||
"""Convert some textual values to boolean."""
|
"""Convert some textual values to boolean."""
|
||||||
if isinstance(obj, (list, tuple)):
|
if isinstance(obj, (list, tuple)):
|
||||||
obj = obj[0]
|
obj = obj[0]
|
||||||
if isinstance(obj, (str, unicode)):
|
if isinstance(obj, str):
|
||||||
obj = obj.lower()
|
obj = obj.lower()
|
||||||
return self._bool_convert.get(obj, obj)
|
return self._bool_convert.get(obj, obj)
|
||||||
|
|
||||||
def arguments_tobool(self):
|
def arguments_tobool(self):
|
||||||
"""Return a dictionary of arguments, converted to booleans where possible."""
|
"""Return a dictionary of arguments, converted to booleans where possible."""
|
||||||
return dict([(k, self.tobool(v)) for k, v in self.arguments.iteritems()])
|
return dict([(k, self.tobool(v)) for k, v in self.arguments.items()])
|
||||||
|
|
||||||
def initialize(self, **kwargs):
|
def initialize(self, **kwargs):
|
||||||
"""Add every passed (key, value) as attributes of the instance."""
|
"""Add every passed (key, value) as attributes of the instance."""
|
||||||
for key, value in kwargs.iteritems():
|
for key, value in kwargs.items():
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_user(self):
|
def current_user(self):
|
||||||
"""Retrieve current user name from the secure cookie."""
|
"""Retrieve current user name from the secure cookie."""
|
||||||
return self.get_secure_cookie("user")
|
current_user = self.get_secure_cookie("user")
|
||||||
|
if isinstance(current_user, bytes):
|
||||||
|
current_user = current_user.decode('utf-8')
|
||||||
|
return current_user
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_user_info(self):
|
def current_user_info(self):
|
||||||
|
@ -174,16 +178,16 @@ class BaseHandler(tornado.web.RequestHandler):
|
||||||
current_user = self.current_user
|
current_user = self.current_user
|
||||||
if current_user in self._users_cache:
|
if current_user in self._users_cache:
|
||||||
return self._users_cache[current_user]
|
return self._users_cache[current_user]
|
||||||
permissions = set([k for (k, v) in self.permissions.iteritems() if v is True])
|
permissions = set([k for (k, v) in self.permissions.items() if v is True])
|
||||||
user_info = {'permissions': permissions}
|
user_info = {'permissions': permissions}
|
||||||
if current_user:
|
if current_user:
|
||||||
user_info['username'] = current_user
|
user_info['_id'] = current_user
|
||||||
res = self.db.query('users', {'username': current_user})
|
user = self.db.getOne('users', {'_id': current_user})
|
||||||
if res:
|
if user:
|
||||||
user = res[0]
|
|
||||||
user_info = user
|
user_info = user
|
||||||
permissions.update(set(user.get('permissions') or []))
|
permissions.update(set(user.get('permissions') or []))
|
||||||
user_info['permissions'] = permissions
|
user_info['permissions'] = permissions
|
||||||
|
user_info['isRegistered'] = True
|
||||||
self._users_cache[current_user] = user_info
|
self._users_cache[current_user] = user_info
|
||||||
return user_info
|
return user_info
|
||||||
|
|
||||||
|
@ -204,7 +208,7 @@ class BaseHandler(tornado.web.RequestHandler):
|
||||||
collection_permission = self.permissions.get(permission)
|
collection_permission = self.permissions.get(permission)
|
||||||
if isinstance(collection_permission, bool):
|
if isinstance(collection_permission, bool):
|
||||||
return collection_permission
|
return collection_permission
|
||||||
if callable(collection_permission):
|
if isinstance(collection_permission, collections.Callable):
|
||||||
return collection_permission(permission)
|
return collection_permission(permission)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -305,7 +309,7 @@ class CollectionHandler(BaseHandler):
|
||||||
:rtype: str"""
|
:rtype: str"""
|
||||||
t = str(time.time()).replace('.', '_')
|
t = str(time.time()).replace('.', '_')
|
||||||
seq = str(self.get_next_seq(seq))
|
seq = str(self.get_next_seq(seq))
|
||||||
rand = ''.join([random.choice(self._id_chars) for x in xrange(random_alpha)])
|
rand = ''.join([random.choice(self._id_chars) for x in range(random_alpha)])
|
||||||
return '-'.join((t, seq, rand))
|
return '-'.join((t, seq, rand))
|
||||||
|
|
||||||
def _filter_results(self, results, params):
|
def _filter_results(self, results, params):
|
||||||
|
@ -320,11 +324,11 @@ class CollectionHandler(BaseHandler):
|
||||||
:rtype: list"""
|
:rtype: list"""
|
||||||
if not params:
|
if not params:
|
||||||
return results
|
return results
|
||||||
params = backend.convert(params)
|
params = monco.convert(params)
|
||||||
filtered = []
|
filtered = []
|
||||||
for result in results:
|
for result in results:
|
||||||
add = True
|
add = True
|
||||||
for key, value in params.iteritems():
|
for key, value in params.items():
|
||||||
if key not in result or result[key] != value:
|
if key not in result or result[key] != value:
|
||||||
add = False
|
add = False
|
||||||
break
|
break
|
||||||
|
@ -338,8 +342,8 @@ class CollectionHandler(BaseHandler):
|
||||||
:param data: dictionary to clean
|
:param data: dictionary to clean
|
||||||
:type data: dict"""
|
:type data: dict"""
|
||||||
if isinstance(data, dict):
|
if isinstance(data, dict):
|
||||||
for key in data.keys():
|
for key in list(data.keys()):
|
||||||
if isinstance(key, (str, unicode)) and key.startswith('$'):
|
if isinstance(key, str) and key.startswith('$'):
|
||||||
del data[key]
|
del data[key]
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@ -349,7 +353,7 @@ class CollectionHandler(BaseHandler):
|
||||||
:param data: dictionary to convert
|
:param data: dictionary to convert
|
||||||
:type data: dict"""
|
:type data: dict"""
|
||||||
ret = {}
|
ret = {}
|
||||||
for key, value in data.iteritems():
|
for key, value in data.items():
|
||||||
if isinstance(value, (list, tuple, dict)):
|
if isinstance(value, (list, tuple, dict)):
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
|
@ -357,7 +361,7 @@ class CollectionHandler(BaseHandler):
|
||||||
key = re_env_key.sub('', key)
|
key = re_env_key.sub('', key)
|
||||||
if not key:
|
if not key:
|
||||||
continue
|
continue
|
||||||
ret[key] = unicode(value).encode(ENCODING)
|
ret[key] = str(value).encode(ENCODING)
|
||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
return ret
|
return ret
|
||||||
|
@ -382,7 +386,7 @@ class CollectionHandler(BaseHandler):
|
||||||
if acl and not self.has_permission(permission):
|
if acl and not self.has_permission(permission):
|
||||||
return self.build_error(status=401, message='insufficient permissions: %s' % permission)
|
return self.build_error(status=401, message='insufficient permissions: %s' % permission)
|
||||||
handler = getattr(self, 'handle_get_%s' % resource, None)
|
handler = getattr(self, 'handle_get_%s' % resource, None)
|
||||||
if handler and callable(handler):
|
if handler and isinstance(handler, collections.Callable):
|
||||||
output = handler(id_, resource_id, **kwargs) or {}
|
output = handler(id_, resource_id, **kwargs) or {}
|
||||||
output = self.apply_filter(output, 'get_%s' % resource)
|
output = self.apply_filter(output, 'get_%s' % resource)
|
||||||
self.write(output)
|
self.write(output)
|
||||||
|
@ -432,7 +436,7 @@ class CollectionHandler(BaseHandler):
|
||||||
return self.build_error(status=401, message='insufficient permissions: %s' % permission)
|
return self.build_error(status=401, message='insufficient permissions: %s' % permission)
|
||||||
# Handle access to sub-resources.
|
# Handle access to sub-resources.
|
||||||
handler = getattr(self, 'handle_%s_%s' % (method, resource), None)
|
handler = getattr(self, 'handle_%s_%s' % (method, resource), None)
|
||||||
if handler and callable(handler):
|
if handler and isinstance(handler, collections.Callable):
|
||||||
data = self.apply_filter(data, 'input_%s_%s' % (method, resource))
|
data = self.apply_filter(data, 'input_%s_%s' % (method, resource))
|
||||||
output = handler(id_, resource_id, data, **kwargs)
|
output = handler(id_, resource_id, data, **kwargs)
|
||||||
output = self.apply_filter(output, 'get_%s' % resource)
|
output = self.apply_filter(output, 'get_%s' % resource)
|
||||||
|
@ -478,7 +482,7 @@ class CollectionHandler(BaseHandler):
|
||||||
if not self.has_permission(permission):
|
if not self.has_permission(permission):
|
||||||
return self.build_error(status=401, message='insufficient permissions: %s' % permission)
|
return self.build_error(status=401, message='insufficient permissions: %s' % permission)
|
||||||
method = getattr(self, 'handle_delete_%s' % resource, None)
|
method = getattr(self, 'handle_delete_%s' % resource, None)
|
||||||
if method and callable(method):
|
if method and isinstance(method, collections.Callable):
|
||||||
output = method(id_, resource_id, **kwargs)
|
output = method(id_, resource_id, **kwargs)
|
||||||
env['RESOURCE'] = resource
|
env['RESOURCE'] = resource
|
||||||
if resource_id:
|
if resource_id:
|
||||||
|
@ -584,7 +588,7 @@ class CollectionHandler(BaseHandler):
|
||||||
ws = yield tornado.websocket.websocket_connect(self.build_ws_url(path))
|
ws = yield tornado.websocket.websocket_connect(self.build_ws_url(path))
|
||||||
ws.write_message(message)
|
ws.write_message(message)
|
||||||
ws.close()
|
ws.close()
|
||||||
except Exception, e:
|
except Exception as e:
|
||||||
self.logger.error('Error yielding WebSocket message: %s', e)
|
self.logger.error('Error yielding WebSocket message: %s', e)
|
||||||
|
|
||||||
|
|
||||||
|
@ -641,7 +645,7 @@ class EventsHandler(CollectionHandler):
|
||||||
if group_id is None:
|
if group_id is None:
|
||||||
return {'persons': persons}
|
return {'persons': persons}
|
||||||
this_persons = [p for p in (this_event.get('tickets') or []) if not p.get('cancelled')]
|
this_persons = [p for p in (this_event.get('tickets') or []) if not p.get('cancelled')]
|
||||||
this_emails = filter(None, [p.get('email') for p in this_persons])
|
this_emails = [_f for _f in [p.get('email') for p in this_persons] if _f]
|
||||||
all_query = {'group_id': group_id}
|
all_query = {'group_id': group_id}
|
||||||
events = self.db.query('events', all_query)
|
events = self.db.query('events', all_query)
|
||||||
for event in events:
|
for event in events:
|
||||||
|
@ -655,7 +659,7 @@ class EventsHandler(CollectionHandler):
|
||||||
or which set of keys specified in a dictionary match their respective values."""
|
or which set of keys specified in a dictionary match their respective values."""
|
||||||
for ticket in tickets:
|
for ticket in tickets:
|
||||||
if isinstance(ticket_id_or_query, dict):
|
if isinstance(ticket_id_or_query, dict):
|
||||||
if all(ticket.get(k) == v for k, v in ticket_id_or_query.iteritems()):
|
if all(ticket.get(k) == v for k, v in ticket_id_or_query.items()):
|
||||||
return ticket
|
return ticket
|
||||||
else:
|
else:
|
||||||
if str(ticket.get('_id')) == ticket_id_or_query:
|
if str(ticket.get('_id')) == ticket_id_or_query:
|
||||||
|
@ -752,7 +756,7 @@ class EventsHandler(CollectionHandler):
|
||||||
ticket = self._get_ticket_data(ticket_id, doc.get('tickets') or [])
|
ticket = self._get_ticket_data(ticket_id, doc.get('tickets') or [])
|
||||||
env = dict(ticket)
|
env = dict(ticket)
|
||||||
env.update({'PERSON_ID': ticket_id, 'TICKED_ID': ticket_id, 'EVENT_ID': id_,
|
env.update({'PERSON_ID': ticket_id, 'TICKED_ID': ticket_id, 'EVENT_ID': id_,
|
||||||
'EVENT_TITLE': doc.get('title', ''), 'WEB_USER': self.current_user,
|
'EVENT_TITLE': doc.get('title', ''), 'WEB_USER': self.current_user_info.get('username', ''),
|
||||||
'WEB_REMOTE_IP': self.request.remote_ip})
|
'WEB_REMOTE_IP': self.request.remote_ip})
|
||||||
stdin_data = {'new': ticket,
|
stdin_data = {'new': ticket,
|
||||||
'event': doc,
|
'event': doc,
|
||||||
|
@ -765,7 +769,7 @@ 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
|
||||||
query = dict([('tickets.%s' % k, v) for k, v in arguments.iteritems()])
|
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
|
||||||
|
@ -794,7 +798,7 @@ class EventsHandler(CollectionHandler):
|
||||||
# always takes the ticket_id from the new ticket
|
# always takes the ticket_id from the new ticket
|
||||||
ticket_id = str(new_ticket_data.get('_id'))
|
ticket_id = str(new_ticket_data.get('_id'))
|
||||||
env.update({'PERSON_ID': ticket_id, 'TICKED_ID': ticket_id, 'EVENT_ID': id_,
|
env.update({'PERSON_ID': ticket_id, 'TICKED_ID': ticket_id, 'EVENT_ID': id_,
|
||||||
'EVENT_TITLE': doc.get('title', ''), 'WEB_USER': self.current_user,
|
'EVENT_TITLE': doc.get('title', ''), 'WEB_USER': self.current_user_info.get('username', ''),
|
||||||
'WEB_REMOTE_IP': self.request.remote_ip})
|
'WEB_REMOTE_IP': self.request.remote_ip})
|
||||||
stdin_data = {'old': old_ticket_data,
|
stdin_data = {'old': old_ticket_data,
|
||||||
'new': new_ticket_data,
|
'new': new_ticket_data,
|
||||||
|
@ -806,7 +810,8 @@ class EventsHandler(CollectionHandler):
|
||||||
if new_ticket_data.get('attended'):
|
if new_ticket_data.get('attended'):
|
||||||
self.run_triggers('attends', stdin_data=stdin_data, env=env)
|
self.run_triggers('attends', stdin_data=stdin_data, env=env)
|
||||||
|
|
||||||
ret = {'action': 'update', '_id': ticket_id, 'ticket': new_ticket_data, 'uuid': uuid}
|
ret = {'action': 'update', '_id': ticket_id, 'ticket': new_ticket_data,
|
||||||
|
'uuid': uuid, 'username': self.current_user_info.get('username', '')}
|
||||||
if old_ticket_data != new_ticket_data:
|
if old_ticket_data != new_ticket_data:
|
||||||
self.send_ws_message('event/%s/tickets/updates' % id_, json.dumps(ret))
|
self.send_ws_message('event/%s/tickets/updates' % id_, json.dumps(ret))
|
||||||
return ret
|
return ret
|
||||||
|
@ -827,7 +832,7 @@ class EventsHandler(CollectionHandler):
|
||||||
self.send_ws_message('event/%s/tickets/updates' % id_, json.dumps(ret))
|
self.send_ws_message('event/%s/tickets/updates' % id_, json.dumps(ret))
|
||||||
env = dict(ticket)
|
env = dict(ticket)
|
||||||
env.update({'PERSON_ID': ticket_id, 'TICKED_ID': ticket_id, 'EVENT_ID': id_,
|
env.update({'PERSON_ID': ticket_id, 'TICKED_ID': ticket_id, 'EVENT_ID': id_,
|
||||||
'EVENT_TITLE': rdoc.get('title', ''), 'WEB_USER': self.current_user,
|
'EVENT_TITLE': rdoc.get('title', ''), 'WEB_USER': self.current_user_info.get('username', ''),
|
||||||
'WEB_REMOTE_IP': self.request.remote_ip})
|
'WEB_REMOTE_IP': self.request.remote_ip})
|
||||||
stdin_data = {'old': ticket,
|
stdin_data = {'old': ticket,
|
||||||
'event': rdoc,
|
'event': rdoc,
|
||||||
|
@ -872,7 +877,7 @@ class UsersHandler(CollectionHandler):
|
||||||
@authenticated
|
@authenticated
|
||||||
def get(self, id_=None, resource=None, resource_id=None, acl=True, **kwargs):
|
def get(self, id_=None, resource=None, resource_id=None, acl=True, **kwargs):
|
||||||
if id_ is not None:
|
if id_ is not None:
|
||||||
if (self.has_permission('user|read') or str(self.current_user_info.get('_id')) == id_):
|
if (self.has_permission('user|read') or self.current_user == id_):
|
||||||
acl = False
|
acl = False
|
||||||
super(UsersHandler, self).get(id_, resource, resource_id, acl=acl, **kwargs)
|
super(UsersHandler, self).get(id_, resource, resource_id, acl=acl, **kwargs)
|
||||||
|
|
||||||
|
@ -896,12 +901,17 @@ class UsersHandler(CollectionHandler):
|
||||||
if new_pwd is not None:
|
if new_pwd is not None:
|
||||||
del data['new_password']
|
del data['new_password']
|
||||||
authorized, user = self.user_authorized(data['username'], old_pwd)
|
authorized, user = self.user_authorized(data['username'], old_pwd)
|
||||||
if not (self.has_permission('user|update') or (authorized and self.current_user == data['username'])):
|
if not (self.has_permission('user|update') or (authorized and
|
||||||
|
self.current_user_info.get('username') == data['username'])):
|
||||||
raise InputException('not authorized to change password')
|
raise InputException('not authorized to change password')
|
||||||
data['password'] = utils.hash_password(new_pwd)
|
data['password'] = utils.hash_password(new_pwd)
|
||||||
if '_id' in data:
|
if '_id' in data:
|
||||||
# Avoid overriding _id
|
|
||||||
del data['_id']
|
del data['_id']
|
||||||
|
if 'username' in data:
|
||||||
|
del data['username']
|
||||||
|
# for the moment, prevent the ability to update permissions via web
|
||||||
|
if 'permissions' in data:
|
||||||
|
del data['permissions']
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
|
@ -909,7 +919,7 @@ class UsersHandler(CollectionHandler):
|
||||||
def put(self, id_=None, resource=None, resource_id=None, **kwargs):
|
def put(self, id_=None, resource=None, resource_id=None, **kwargs):
|
||||||
if id_ is None:
|
if id_ is None:
|
||||||
return self.build_error(status=404, message='unable to access the resource')
|
return self.build_error(status=404, message='unable to access the resource')
|
||||||
if not (self.has_permission('user|update') or str(self.current_user_info.get('_id')) == id_):
|
if not (self.has_permission('user|update') or self.current_user == id_):
|
||||||
return self.build_error(status=401, message='insufficient permissions: user|update or current user')
|
return self.build_error(status=401, message='insufficient permissions: user|update or current user')
|
||||||
super(UsersHandler, self).put(id_, resource, resource_id, **kwargs)
|
super(UsersHandler, self).put(id_, resource, resource_id, **kwargs)
|
||||||
|
|
||||||
|
@ -970,7 +980,7 @@ class EbCSVImportPersonsHandler(BaseHandler):
|
||||||
#[x.get('email') for x in (event_details[0].get('tickets') or []) if x.get('email')])
|
#[x.get('email') for x in (event_details[0].get('tickets') or []) if x.get('email')])
|
||||||
for ticket in (event_details[0].get('tickets') or []):
|
for ticket in (event_details[0].get('tickets') or []):
|
||||||
all_emails.add('%s_%s_%s' % (ticket.get('name'), ticket.get('surname'), ticket.get('email')))
|
all_emails.add('%s_%s_%s' % (ticket.get('name'), ticket.get('surname'), ticket.get('email')))
|
||||||
for fieldname, contents in self.request.files.iteritems():
|
for fieldname, contents in self.request.files.items():
|
||||||
for content in contents:
|
for content in contents:
|
||||||
filename = content['filename']
|
filename = content['filename']
|
||||||
parseStats, persons = utils.csvParse(content['body'], remap=self.csvRemap)
|
parseStats, persons = utils.csvParse(content['body'], remap=self.csvRemap)
|
||||||
|
@ -1038,7 +1048,7 @@ class WebSocketEventUpdatesHandler(tornado.websocket.WebSocketHandler):
|
||||||
logging.debug('WebSocketEventUpdatesHandler.on_message url:%s' % url)
|
logging.debug('WebSocketEventUpdatesHandler.on_message url:%s' % url)
|
||||||
count = 0
|
count = 0
|
||||||
_to_delete = set()
|
_to_delete = set()
|
||||||
for uuid, client in _ws_clients.get(url, {}).iteritems():
|
for uuid, client in _ws_clients.get(url, {}).items():
|
||||||
try:
|
try:
|
||||||
client.write_message(message)
|
client.write_message(message)
|
||||||
except:
|
except:
|
||||||
|
@ -1079,10 +1089,11 @@ class LoginHandler(RootHandler):
|
||||||
self.write({'error': True, 'message': 'missing username or password'})
|
self.write({'error': True, 'message': 'missing username or password'})
|
||||||
return
|
return
|
||||||
authorized, user = self.user_authorized(username, password)
|
authorized, user = self.user_authorized(username, password)
|
||||||
if authorized and user.get('username'):
|
if authorized and 'username' in user and '_id' in user:
|
||||||
|
id_ = str(user['_id'])
|
||||||
username = user['username']
|
username = user['username']
|
||||||
logging.info('successful login for user %s' % username)
|
logging.info('successful login for user %s (id: %s)' % (username, id_))
|
||||||
self.set_secure_cookie("user", username)
|
self.set_secure_cookie("user", id_)
|
||||||
self.write({'error': False, 'message': 'successful login'})
|
self.write({'error': False, 'message': 'successful login'})
|
||||||
return
|
return
|
||||||
logging.info('login failed for user %s' % username)
|
logging.info('login failed for user %s' % username)
|
||||||
|
@ -1132,7 +1143,7 @@ def run():
|
||||||
ssl_options = dict(certfile=options.ssl_cert, keyfile=options.ssl_key)
|
ssl_options = dict(certfile=options.ssl_cert, keyfile=options.ssl_key)
|
||||||
|
|
||||||
# database backend connector
|
# database backend connector
|
||||||
db_connector = backend.EventManDB(url=options.mongo_url, dbName=options.db_name)
|
db_connector = monco.Monco(url=options.mongo_url, dbName=options.db_name)
|
||||||
init_params = dict(db=db_connector, data_dir=options.data_dir, listen_port=options.port,
|
init_params = dict(db=db_connector, data_dir=options.data_dir, listen_port=options.port,
|
||||||
authentication=options.authentication, logger=logger, ssl_options=ssl_options)
|
authentication=options.authentication, logger=logger, ssl_options=ssl_options)
|
||||||
|
|
||||||
|
@ -1173,7 +1184,7 @@ def run():
|
||||||
],
|
],
|
||||||
template_path=os.path.join(os.path.dirname(__file__), "templates"),
|
template_path=os.path.join(os.path.dirname(__file__), "templates"),
|
||||||
static_path=os.path.join(os.path.dirname(__file__), "static"),
|
static_path=os.path.join(os.path.dirname(__file__), "static"),
|
||||||
cookie_secret='__COOKIE_SECRET__',
|
cookie_secret=cookie_secret,
|
||||||
login_url='/login',
|
login_url='/login',
|
||||||
debug=options.debug)
|
debug=options.debug)
|
||||||
http_server = tornado.httpserver.HTTPServer(application, ssl_options=ssl_options or None)
|
http_server = tornado.httpserver.HTTPServer(application, ssl_options=ssl_options or None)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
"""EventMan(ager) database backend
|
"""Monco: a MongoDB database backend
|
||||||
|
|
||||||
Classes and functions used to manage events and attendees database.
|
Classes and functions used to issue queries to a MongoDB database.
|
||||||
|
|
||||||
Copyright 2015-2016 Davide Alberani <da@erlug.linux.it>
|
Copyright 2016-2017 Davide Alberani <da@erlug.linux.it>
|
||||||
RaspiBO <info@raspibo.org>
|
RaspiBO <info@raspibo.org>
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -23,6 +23,7 @@ from bson.objectid import ObjectId
|
||||||
re_objectid = re.compile(r'[0-9a-f]{24}')
|
re_objectid = re.compile(r'[0-9a-f]{24}')
|
||||||
|
|
||||||
_force_conversion = {
|
_force_conversion = {
|
||||||
|
'_id': ObjectId,
|
||||||
'seq_hex': str,
|
'seq_hex': str,
|
||||||
'tickets.seq_hex': str
|
'tickets.seq_hex': str
|
||||||
}
|
}
|
||||||
|
@ -40,7 +41,8 @@ def convert_obj(obj):
|
||||||
if isinstance(obj, bool):
|
if isinstance(obj, bool):
|
||||||
return obj
|
return obj
|
||||||
try:
|
try:
|
||||||
return ObjectId(obj)
|
if re_objectid.match(obj):
|
||||||
|
return ObjectId(obj)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return obj
|
return obj
|
||||||
|
@ -56,9 +58,12 @@ def convert(seq):
|
||||||
"""
|
"""
|
||||||
if isinstance(seq, dict):
|
if isinstance(seq, dict):
|
||||||
d = {}
|
d = {}
|
||||||
for key, item in seq.iteritems():
|
for key, item in seq.items():
|
||||||
if key in _force_conversion:
|
if key in _force_conversion:
|
||||||
d[key] = _force_conversion[key](item)
|
try:
|
||||||
|
d[key] = _force_conversion[key](item)
|
||||||
|
except:
|
||||||
|
d[key] = item
|
||||||
else:
|
else:
|
||||||
d[key] = convert(item)
|
d[key] = convert(item)
|
||||||
return d
|
return d
|
||||||
|
@ -67,23 +72,35 @@ def convert(seq):
|
||||||
return convert_obj(seq)
|
return convert_obj(seq)
|
||||||
|
|
||||||
|
|
||||||
class EventManDB(object):
|
class MoncoError(Exception):
|
||||||
|
"""Base class for Monco exceptions."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MoncoConnectionError(MoncoError):
|
||||||
|
"""Monco exceptions raise when a connection problem occurs."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Monco(object):
|
||||||
"""MongoDB connector."""
|
"""MongoDB connector."""
|
||||||
db = None
|
db = None
|
||||||
connection = None
|
connection = None
|
||||||
|
|
||||||
# map operations on lists of items.
|
# map operations on lists of items.
|
||||||
_operations = {
|
_operations = {
|
||||||
'update': '$set',
|
'update': '$set',
|
||||||
'append': '$push',
|
'append': '$push',
|
||||||
'appendUnique': '$addToSet',
|
'appendUnique': '$addToSet',
|
||||||
'delete': '$pull',
|
'delete': '$pull',
|
||||||
'increment': '$inc'
|
'increment': '$inc'
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, url=None, dbName='eventman'):
|
def __init__(self, dbName, url=None):
|
||||||
"""Initialize the instance, connecting to the database.
|
"""Initialize the instance, connecting to the database.
|
||||||
|
|
||||||
|
:param dbName: name of the database
|
||||||
|
:type dbName: str (or None to use the dbName passed at initialization)
|
||||||
:param url: URL of the database
|
:param url: URL of the database
|
||||||
:type url: str (or None to connect to localhost)
|
:type url: str (or None to connect to localhost)
|
||||||
"""
|
"""
|
||||||
|
@ -91,9 +108,11 @@ class EventManDB(object):
|
||||||
self._dbName = dbName
|
self._dbName = dbName
|
||||||
self.connect(url)
|
self.connect(url)
|
||||||
|
|
||||||
def connect(self, url=None, dbName=None):
|
def connect(self, dbName=None, url=None):
|
||||||
"""Connect to the database.
|
"""Connect to the database.
|
||||||
|
|
||||||
|
:param dbName: name of the database
|
||||||
|
:type dbName: str (or None to use the dbName passed at initialization)
|
||||||
:param url: URL of the database
|
:param url: URL of the database
|
||||||
:type url: str (or None to connect to localhost)
|
:type url: str (or None to connect to localhost)
|
||||||
|
|
||||||
|
@ -106,10 +125,26 @@ class EventManDB(object):
|
||||||
self._url = url
|
self._url = url
|
||||||
if dbName:
|
if dbName:
|
||||||
self._dbName = dbName
|
self._dbName = dbName
|
||||||
|
if not self._dbName:
|
||||||
|
raise MoncoConnectionError('no database name specified')
|
||||||
self.connection = pymongo.MongoClient(self._url)
|
self.connection = pymongo.MongoClient(self._url)
|
||||||
self.db = self.connection[self._dbName]
|
self.db = self.connection[self._dbName]
|
||||||
return self.db
|
return self.db
|
||||||
|
|
||||||
|
def getOne(self, collection, query=None):
|
||||||
|
"""Get a single document with the specified `query`.
|
||||||
|
|
||||||
|
:param collection: search the document in this collection
|
||||||
|
:type collection: str
|
||||||
|
:param query: query to filter the documents
|
||||||
|
:type query: dict or None
|
||||||
|
|
||||||
|
:returns: the first document matching the query
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
results = self.query(collection, convert(query))
|
||||||
|
return results and results[0] or {}
|
||||||
|
|
||||||
def get(self, collection, _id):
|
def get(self, collection, _id):
|
||||||
"""Get a single document with the specified `_id`.
|
"""Get a single document with the specified `_id`.
|
||||||
|
|
||||||
|
@ -121,8 +156,7 @@ class EventManDB(object):
|
||||||
:returns: the document with the given `_id`
|
:returns: the document with the given `_id`
|
||||||
:rtype: dict
|
:rtype: dict
|
||||||
"""
|
"""
|
||||||
results = self.query(collection, convert({'_id': _id}))
|
return self.getOne(collection, {'_id': _id})
|
||||||
return results and results[0] or {}
|
|
||||||
|
|
||||||
def query(self, collection, query=None, condition='or'):
|
def query(self, collection, query=None, condition='or'):
|
||||||
"""Get multiple documents matching a query.
|
"""Get multiple documents matching a query.
|
||||||
|
@ -130,7 +164,7 @@ class EventManDB(object):
|
||||||
:param collection: search for documents in this collection
|
:param collection: search for documents in this collection
|
||||||
:type collection: str
|
:type collection: str
|
||||||
:param query: search for documents with those attributes
|
:param query: search for documents with those attributes
|
||||||
:type query: dict or None
|
:type query: dict, list or None
|
||||||
|
|
||||||
:returns: list of matching documents
|
:returns: list of matching documents
|
||||||
:rtype: list
|
:rtype: list
|
||||||
|
@ -222,7 +256,7 @@ class EventManDB(object):
|
||||||
operator = self._operations.get(operation)
|
operator = self._operations.get(operation)
|
||||||
if updateList:
|
if updateList:
|
||||||
newData = {}
|
newData = {}
|
||||||
for key, value in data.iteritems():
|
for key, value in data.items():
|
||||||
newData['%s.$.%s' % (updateList, key)] = value
|
newData['%s.$.%s' % (updateList, key)] = value
|
||||||
data = newData
|
data = newData
|
||||||
res = db[collection].find_and_modify(query=_id_or_query,
|
res = db[collection].find_and_modify(query=_id_or_query,
|
||||||
|
@ -230,6 +264,30 @@ class EventManDB(object):
|
||||||
lastErrorObject = res.get('lastErrorObject') or {}
|
lastErrorObject = res.get('lastErrorObject') or {}
|
||||||
return lastErrorObject.get('updatedExisting', False), res.get('value') or {}
|
return lastErrorObject.get('updatedExisting', False), res.get('value') or {}
|
||||||
|
|
||||||
|
def updateMany(self, collection, query, data):
|
||||||
|
"""Update multiple existing documents.
|
||||||
|
|
||||||
|
query can be an ID or a dict representing a query.
|
||||||
|
|
||||||
|
:param collection: update documents in this collection
|
||||||
|
:type collection: str
|
||||||
|
:param query: a query or a list of attributes in the data that must match
|
||||||
|
:type query: str or :class:`~bson.objectid.ObjectId` or iterable
|
||||||
|
:param data: the updated information to store
|
||||||
|
:type data: dict
|
||||||
|
|
||||||
|
:returns: a dict with the success state and number of updated items
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
db = self.connect()
|
||||||
|
data = convert(data or {})
|
||||||
|
query = convert(query)
|
||||||
|
if not isinstance(query, dict):
|
||||||
|
query = {'_id': query}
|
||||||
|
if '_id' in data:
|
||||||
|
del data['_id']
|
||||||
|
return db[collection].update(query, {'$set': data}, multi=True)
|
||||||
|
|
||||||
def delete(self, collection, _id_or_query=None, force=False):
|
def delete(self, collection, _id_or_query=None, force=False):
|
||||||
"""Remove one or more documents from a collection.
|
"""Remove one or more documents from a collection.
|
||||||
|
|
||||||
|
@ -240,8 +298,8 @@ class EventManDB(object):
|
||||||
:param force: force the deletion of all documents, when `_id_or_query` is empty
|
:param force: force the deletion of all documents, when `_id_or_query` is empty
|
||||||
:type force: bool
|
:type force: bool
|
||||||
|
|
||||||
:returns: how many documents were removed
|
:returns: dictionary with the number or removed documents
|
||||||
:rtype: int
|
:rtype: dict
|
||||||
"""
|
"""
|
||||||
if not _id_or_query and not force:
|
if not _id_or_query and not force:
|
||||||
return
|
return
|
||||||
|
@ -250,4 +308,3 @@ class EventManDB(object):
|
||||||
_id_or_query = {'_id': _id_or_query}
|
_id_or_query = {'_id': _id_or_query}
|
||||||
_id_or_query = convert(_id_or_query)
|
_id_or_query = convert(_id_or_query)
|
||||||
return db[collection].remove(_id_or_query)
|
return db[collection].remove(_id_or_query)
|
||||||
|
|
183
static/js/angular-websocket.js
vendored
183
static/js/angular-websocket.js
vendored
|
@ -1,28 +1,72 @@
|
||||||
(function() {
|
(function (global, factory) {
|
||||||
|
if (typeof define === "function" && define.amd) {
|
||||||
|
define(['module', 'exports', 'angular', 'ws'], factory);
|
||||||
|
} else if (typeof exports !== "undefined") {
|
||||||
|
factory(module, exports, require('angular'), require('ws'));
|
||||||
|
} else {
|
||||||
|
var mod = {
|
||||||
|
exports: {}
|
||||||
|
};
|
||||||
|
factory(mod, mod.exports, global.angular, global.ws);
|
||||||
|
global.angularWebsocket = mod.exports;
|
||||||
|
}
|
||||||
|
})(this, function (module, exports, _angular, ws) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var noop = angular.noop;
|
Object.defineProperty(exports, "__esModule", {
|
||||||
var objectFreeze = (Object.freeze) ? Object.freeze : noop;
|
value: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var _angular2 = _interopRequireDefault(_angular);
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) {
|
||||||
|
return obj && obj.__esModule ? obj : {
|
||||||
|
default: obj
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
|
||||||
|
return typeof obj;
|
||||||
|
} : function (obj) {
|
||||||
|
return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
var Socket;
|
||||||
|
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
try {
|
||||||
|
|
||||||
|
Socket = ws.Client || ws.client || ws;
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Browser
|
||||||
|
Socket = Socket || window.WebSocket || window.MozWebSocket;
|
||||||
|
|
||||||
|
var noop = _angular2.default.noop;
|
||||||
|
var objectFreeze = Object.freeze ? Object.freeze : noop;
|
||||||
var objectDefineProperty = Object.defineProperty;
|
var objectDefineProperty = Object.defineProperty;
|
||||||
var isString = angular.isString;
|
var isString = _angular2.default.isString;
|
||||||
var isFunction = angular.isFunction;
|
var isFunction = _angular2.default.isFunction;
|
||||||
var isDefined = angular.isDefined;
|
var isDefined = _angular2.default.isDefined;
|
||||||
var isObject = angular.isObject;
|
var isObject = _angular2.default.isObject;
|
||||||
var isArray = angular.isArray;
|
var isArray = _angular2.default.isArray;
|
||||||
var forEach = angular.forEach;
|
var forEach = _angular2.default.forEach;
|
||||||
var arraySlice = Array.prototype.slice;
|
var arraySlice = Array.prototype.slice;
|
||||||
// ie8 wat
|
// ie8 wat
|
||||||
if (!Array.prototype.indexOf) {
|
if (!Array.prototype.indexOf) {
|
||||||
Array.prototype.indexOf = function(elt /*, from*/) {
|
Array.prototype.indexOf = function (elt /*, from*/) {
|
||||||
var len = this.length >>> 0;
|
var len = this.length >>> 0;
|
||||||
var from = Number(arguments[1]) || 0;
|
var from = Number(arguments[1]) || 0;
|
||||||
from = (from < 0) ? Math.ceil(from) : Math.floor(from);
|
from = from < 0 ? Math.ceil(from) : Math.floor(from);
|
||||||
if (from < 0) {
|
if (from < 0) {
|
||||||
from += len;
|
from += len;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; from < len; from++) {
|
for (; from < len; from++) {
|
||||||
if (from in this && this[from] === elt) { return from; }
|
if (from in this && this[from] === elt) {
|
||||||
|
return from;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
|
@ -48,20 +92,20 @@
|
||||||
// this.buffer = [];
|
// this.buffer = [];
|
||||||
|
|
||||||
// TODO: refactor options to use isDefined
|
// TODO: refactor options to use isDefined
|
||||||
this.scope = options && options.scope || $rootScope;
|
this.scope = options && options.scope || $rootScope;
|
||||||
this.rootScopeFailover = options && options.rootScopeFailover && true;
|
this.rootScopeFailover = options && options.rootScopeFailover && true;
|
||||||
this.useApplyAsync = options && options.useApplyAsync || false;
|
this.useApplyAsync = options && options.useApplyAsync || false;
|
||||||
this.initialTimeout = options && options.initialTimeout || 500; // 500ms
|
this.initialTimeout = options && options.initialTimeout || 500; // 500ms
|
||||||
this.maxTimeout = options && options.maxTimeout || 5 * 60 * 1000; // 5 minutes
|
this.maxTimeout = options && options.maxTimeout || 5 * 60 * 1000; // 5 minutes
|
||||||
this.reconnectIfNotNormalClose = options && options.reconnectIfNotNormalClose || false;
|
this.reconnectIfNotNormalClose = options && options.reconnectIfNotNormalClose || false;
|
||||||
this.binaryType = options && options.binaryType || 'blob';
|
this.binaryType = options && options.binaryType || 'blob';
|
||||||
|
|
||||||
this._reconnectAttempts = 0;
|
this._reconnectAttempts = 0;
|
||||||
this.sendQueue = [];
|
this.sendQueue = [];
|
||||||
this.onOpenCallbacks = [];
|
this.onOpenCallbacks = [];
|
||||||
this.onMessageCallbacks = [];
|
this.onMessageCallbacks = [];
|
||||||
this.onErrorCallbacks = [];
|
this.onErrorCallbacks = [];
|
||||||
this.onCloseCallbacks = [];
|
this.onCloseCallbacks = [];
|
||||||
|
|
||||||
objectFreeze(this._readyStateConstants);
|
objectFreeze(this._readyStateConstants);
|
||||||
|
|
||||||
|
@ -70,10 +114,8 @@
|
||||||
} else {
|
} else {
|
||||||
this._setInternalState(0);
|
this._setInternalState(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$WebSocket.prototype._readyStateConstants = {
|
$WebSocket.prototype._readyStateConstants = {
|
||||||
'CONNECTING': 0,
|
'CONNECTING': 0,
|
||||||
'OPEN': 1,
|
'OPEN': 1,
|
||||||
|
@ -84,9 +126,7 @@
|
||||||
|
|
||||||
$WebSocket.prototype._normalCloseCode = 1000;
|
$WebSocket.prototype._normalCloseCode = 1000;
|
||||||
|
|
||||||
$WebSocket.prototype._reconnectableStatusCodes = [
|
$WebSocket.prototype._reconnectableStatusCodes = [4000];
|
||||||
4000
|
|
||||||
];
|
|
||||||
|
|
||||||
$WebSocket.prototype.safeDigest = function safeDigest(autoApply) {
|
$WebSocket.prototype.safeDigest = function safeDigest(autoApply) {
|
||||||
if (autoApply && !this.scope.$$phase) {
|
if (autoApply && !this.scope.$$phase) {
|
||||||
|
@ -99,7 +139,7 @@
|
||||||
if (scope) {
|
if (scope) {
|
||||||
this.scope = scope;
|
this.scope = scope;
|
||||||
if (this.rootScopeFailover) {
|
if (this.rootScopeFailover) {
|
||||||
this.scope.$on('$destroy', function() {
|
this.scope.$on('$destroy', function () {
|
||||||
self.scope = $rootScope;
|
self.scope = $rootScope;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -110,10 +150,10 @@
|
||||||
$WebSocket.prototype._connect = function _connect(force) {
|
$WebSocket.prototype._connect = function _connect(force) {
|
||||||
if (force || !this.socket || this.socket.readyState !== this._readyStateConstants.OPEN) {
|
if (force || !this.socket || this.socket.readyState !== this._readyStateConstants.OPEN) {
|
||||||
this.socket = $websocketBackend.create(this.url, this.protocols);
|
this.socket = $websocketBackend.create(this.url, this.protocols);
|
||||||
this.socket.onmessage = angular.bind(this, this._onMessageHandler);
|
this.socket.onmessage = _angular2.default.bind(this, this._onMessageHandler);
|
||||||
this.socket.onopen = angular.bind(this, this._onOpenHandler);
|
this.socket.onopen = _angular2.default.bind(this, this._onOpenHandler);
|
||||||
this.socket.onerror = angular.bind(this, this._onErrorHandler);
|
this.socket.onerror = _angular2.default.bind(this, this._onErrorHandler);
|
||||||
this.socket.onclose = angular.bind(this, this._onCloseHandler);
|
this.socket.onclose = _angular2.default.bind(this, this._onCloseHandler);
|
||||||
this.socket.binaryType = this.binaryType;
|
this.socket.binaryType = this.binaryType;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -122,9 +162,7 @@
|
||||||
while (this.sendQueue.length && this.socket.readyState === this._readyStateConstants.OPEN) {
|
while (this.sendQueue.length && this.socket.readyState === this._readyStateConstants.OPEN) {
|
||||||
var data = this.sendQueue.shift();
|
var data = this.sendQueue.shift();
|
||||||
|
|
||||||
this.socket.send(
|
this.socket.send(isString(data.message) || this.binaryType != 'blob' ? data.message : JSON.stringify(data.message));
|
||||||
isString(data.message) || this.binaryType != "blob" ? data.message : JSON.stringify(data.message)
|
|
||||||
);
|
|
||||||
data.deferred.resolve();
|
data.deferred.resolve();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -162,7 +200,6 @@
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
$WebSocket.prototype.onMessage = function onMessage(callback, options) {
|
$WebSocket.prototype.onMessage = function onMessage(callback, options) {
|
||||||
if (!isFunction(callback)) {
|
if (!isFunction(callback)) {
|
||||||
throw new Error('Callback must be a function');
|
throw new Error('Callback must be a function');
|
||||||
|
@ -189,14 +226,14 @@
|
||||||
$WebSocket.prototype._onCloseHandler = function _onCloseHandler(event) {
|
$WebSocket.prototype._onCloseHandler = function _onCloseHandler(event) {
|
||||||
var self = this;
|
var self = this;
|
||||||
if (self.useApplyAsync) {
|
if (self.useApplyAsync) {
|
||||||
self.scope.$applyAsync(function() {
|
self.scope.$applyAsync(function () {
|
||||||
self.notifyCloseCallbacks(event);
|
self.notifyCloseCallbacks(event);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
self.notifyCloseCallbacks(event);
|
self.notifyCloseCallbacks(event);
|
||||||
self.safeDigest(autoApply);
|
self.safeDigest(true);
|
||||||
}
|
}
|
||||||
if ((this.reconnectIfNotNormalClose && event.code !== this._normalCloseCode) || this._reconnectableStatusCodes.indexOf(event.code) > -1) {
|
if (this.reconnectIfNotNormalClose && event.code !== this._normalCloseCode || this._reconnectableStatusCodes.indexOf(event.code) > -1) {
|
||||||
this.reconnect();
|
this.reconnect();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -204,12 +241,12 @@
|
||||||
$WebSocket.prototype._onErrorHandler = function _onErrorHandler(event) {
|
$WebSocket.prototype._onErrorHandler = function _onErrorHandler(event) {
|
||||||
var self = this;
|
var self = this;
|
||||||
if (self.useApplyAsync) {
|
if (self.useApplyAsync) {
|
||||||
self.scope.$applyAsync(function() {
|
self.scope.$applyAsync(function () {
|
||||||
self.notifyErrorCallbacks(event);
|
self.notifyErrorCallbacks(event);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
self.notifyErrorCallbacks(event);
|
self.notifyErrorCallbacks(event);
|
||||||
self.safeDigest(autoApply);
|
self.safeDigest(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -223,12 +260,10 @@
|
||||||
if (pattern) {
|
if (pattern) {
|
||||||
if (isString(pattern) && message.data === pattern) {
|
if (isString(pattern) && message.data === pattern) {
|
||||||
applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message);
|
applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message);
|
||||||
}
|
} else if (pattern instanceof RegExp && pattern.exec(message.data)) {
|
||||||
else if (pattern instanceof RegExp && pattern.exec(message.data)) {
|
|
||||||
applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message);
|
applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message);
|
applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,7 +271,7 @@
|
||||||
function applyAsyncOrDigest(callback, autoApply, args) {
|
function applyAsyncOrDigest(callback, autoApply, args) {
|
||||||
args = arraySlice.call(arguments, 2);
|
args = arraySlice.call(arguments, 2);
|
||||||
if (self.useApplyAsync) {
|
if (self.useApplyAsync) {
|
||||||
self.scope.$applyAsync(function() {
|
self.scope.$applyAsync(function () {
|
||||||
callback.apply(self, args);
|
callback.apply(self, args);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -244,7 +279,6 @@
|
||||||
self.safeDigest(autoApply);
|
self.safeDigest(autoApply);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$WebSocket.prototype.close = function close(force) {
|
$WebSocket.prototype.close = function close(force) {
|
||||||
|
@ -261,8 +295,7 @@
|
||||||
|
|
||||||
if (self.readyState === self._readyStateConstants.RECONNECT_ABORTED) {
|
if (self.readyState === self._readyStateConstants.RECONNECT_ABORTED) {
|
||||||
deferred.reject('Socket connection has been closed');
|
deferred.reject('Socket connection has been closed');
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
self.sendQueue.push({
|
self.sendQueue.push({
|
||||||
message: data,
|
message: data,
|
||||||
deferred: deferred
|
deferred: deferred
|
||||||
|
@ -274,7 +307,7 @@
|
||||||
function cancelableify(promise) {
|
function cancelableify(promise) {
|
||||||
promise.cancel = cancel;
|
promise.cancel = cancel;
|
||||||
var then = promise.then;
|
var then = promise.then;
|
||||||
promise.then = function() {
|
promise.then = function () {
|
||||||
var newPromise = then.apply(this, arguments);
|
var newPromise = then.apply(this, arguments);
|
||||||
return cancelableify(newPromise);
|
return cancelableify(newPromise);
|
||||||
};
|
};
|
||||||
|
@ -287,8 +320,7 @@
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($websocketBackend.isMocked && $websocketBackend.isMocked() &&
|
if ($websocketBackend.isMocked && $websocketBackend.isMocked() && $websocketBackend.isConnected(this.url)) {
|
||||||
$websocketBackend.isConnected(this.url)) {
|
|
||||||
this._onMessageHandler($websocketBackend.mockSend());
|
this._onMessageHandler($websocketBackend.mockSend());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,7 +335,7 @@
|
||||||
var backoffDelaySeconds = backoffDelay / 1000;
|
var backoffDelaySeconds = backoffDelay / 1000;
|
||||||
console.log('Reconnecting in ' + backoffDelaySeconds + ' seconds');
|
console.log('Reconnecting in ' + backoffDelaySeconds + ' seconds');
|
||||||
|
|
||||||
$timeout(angular.bind(this, this._connect), backoffDelay);
|
$timeout(_angular2.default.bind(this, this._connect), backoffDelay);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
@ -330,8 +362,7 @@
|
||||||
}
|
}
|
||||||
this._internalConnectionState = state;
|
this._internalConnectionState = state;
|
||||||
|
|
||||||
|
forEach(this.sendQueue, function (pending) {
|
||||||
forEach(this.sendQueue, function(pending) {
|
|
||||||
pending.deferred.reject('Message cancelled due to closed socket connection');
|
pending.deferred.reject('Message cancelled due to closed socket connection');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -339,62 +370,46 @@
|
||||||
// Read only .readyState
|
// Read only .readyState
|
||||||
if (objectDefineProperty) {
|
if (objectDefineProperty) {
|
||||||
objectDefineProperty($WebSocket.prototype, 'readyState', {
|
objectDefineProperty($WebSocket.prototype, 'readyState', {
|
||||||
get: function() {
|
get: function get() {
|
||||||
return this._internalConnectionState || this.socket.readyState;
|
return this._internalConnectionState || this.socket.readyState;
|
||||||
},
|
},
|
||||||
set: function() {
|
set: function set() {
|
||||||
throw new Error('The readyState property is read-only');
|
throw new Error('The readyState property is read-only');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return function(url, protocols, options) {
|
return function (url, protocols, options) {
|
||||||
return new $WebSocket(url, protocols, options);
|
return new $WebSocket(url, protocols, options);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// $WebSocketBackendProvider.$inject = ['$window', '$log'];
|
// $WebSocketBackendProvider.$inject = ['$log'];
|
||||||
function $WebSocketBackendProvider($window, $log) {
|
function $WebSocketBackendProvider($log) {
|
||||||
this.create = function create(url, protocols) {
|
this.create = function create(url, protocols) {
|
||||||
var match = /wss?:\/\//.exec(url);
|
var match = /wss?:\/\//.exec(url);
|
||||||
var Socket, ws;
|
|
||||||
if (!match) {
|
if (!match) {
|
||||||
throw new Error('Invalid url provided');
|
throw new Error('Invalid url provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommonJS
|
|
||||||
if (typeof exports === 'object' && require) {
|
|
||||||
try {
|
|
||||||
ws = require('ws');
|
|
||||||
Socket = (ws.Client || ws.client || ws);
|
|
||||||
} catch(e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Browser
|
|
||||||
Socket = Socket || $window.WebSocket || $window.MozWebSocket;
|
|
||||||
|
|
||||||
if (protocols) {
|
if (protocols) {
|
||||||
return new Socket(url, protocols);
|
return new Socket(url, protocols);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Socket(url);
|
return new Socket(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.createWebSocketBackend = function createWebSocketBackend(url, protocols) {
|
this.createWebSocketBackend = function createWebSocketBackend(url, protocols) {
|
||||||
$log.warn('Deprecated: Please use .create(url, protocols)');
|
$log.warn('Deprecated: Please use .create(url, protocols)');
|
||||||
return this.create(url, protocols);
|
return this.create(url, protocols);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
angular.module('ngWebSocket', [])
|
_angular2.default.module('ngWebSocket', []).factory('$websocket', ['$rootScope', '$q', '$timeout', '$websocketBackend', $WebSocketProvider]).factory('WebSocket', ['$rootScope', '$q', '$timeout', 'WebsocketBackend', $WebSocketProvider]).service('$websocketBackend', ['$log', $WebSocketBackendProvider]).service('WebSocketBackend', ['$log', $WebSocketBackendProvider]);
|
||||||
.factory('$websocket', ['$rootScope', '$q', '$timeout', '$websocketBackend', $WebSocketProvider])
|
|
||||||
.factory('WebSocket', ['$rootScope', '$q', '$timeout', 'WebsocketBackend', $WebSocketProvider])
|
|
||||||
.service('$websocketBackend', ['$window', '$log', $WebSocketBackendProvider])
|
|
||||||
.service('WebSocketBackend', ['$window', '$log', $WebSocketBackendProvider]);
|
|
||||||
|
|
||||||
|
_angular2.default.module('angular-websocket', ['ngWebSocket']);
|
||||||
|
|
||||||
angular.module('angular-websocket', ['ngWebSocket']);
|
exports.default = _angular2.default.module('ngWebSocket');
|
||||||
|
module.exports = exports['default'];
|
||||||
if (typeof module === 'object' && typeof define !== 'function') {
|
});
|
||||||
module.exports = angular.module('ngWebSocket');
|
|
||||||
}
|
|
||||||
}());
|
|
||||||
|
|
2
static/js/angular-websocket.min.js
vendored
Normal file
2
static/js/angular-websocket.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
tools/monco.py
Symbolic link
1
tools/monco.py
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../monco.py
|
50
tools/qrcode_reader.py
Executable file
50
tools/qrcode_reader.py
Executable file
|
@ -0,0 +1,50 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import io
|
||||||
|
import serial
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
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 checkin(self, code):
|
||||||
|
req = self.session.put(self.checkin_url + '?order_nr=' + code[:9], json={'attended': True}, verify=False)
|
||||||
|
req.raise_for_status()
|
||||||
|
req.connection.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def scan():
|
||||||
|
ser = serial.Serial(port='/dev/ttyACM0', timeout=1)
|
||||||
|
ser_io = io.TextIOWrapper(io.BufferedRWPair(ser, ser, 1), newline='\r', line_buffering=True)
|
||||||
|
while True:
|
||||||
|
line = ser_io.readline().strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
yield line
|
||||||
|
|
||||||
|
|
||||||
|
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')
|
||||||
|
try:
|
||||||
|
for code in scan():
|
||||||
|
print(code)
|
||||||
|
connector.checkin(code)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print('exiting...')
|
25
utils.py
25
utils.py
|
@ -22,7 +22,7 @@ import string
|
||||||
import random
|
import random
|
||||||
import hashlib
|
import hashlib
|
||||||
import datetime
|
import datetime
|
||||||
import StringIO
|
import io
|
||||||
from bson.objectid import ObjectId
|
from bson.objectid import ObjectId
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,7 +39,9 @@ def csvParse(csvStr, remap=None, merge=None):
|
||||||
:returns: tuple with a dict of total and valid lines and the data
|
:returns: tuple with a dict of total and valid lines and the data
|
||||||
:rtype: tuple
|
:rtype: tuple
|
||||||
"""
|
"""
|
||||||
fd = StringIO.StringIO(csvStr)
|
if isinstance(csvStr, bytes):
|
||||||
|
csvStr = csvStr.decode('utf-8')
|
||||||
|
fd = io.StringIO(csvStr)
|
||||||
reader = csv.reader(fd)
|
reader = csv.reader(fd)
|
||||||
remap = remap or {}
|
remap = remap or {}
|
||||||
merge = merge or {}
|
merge = merge or {}
|
||||||
|
@ -47,7 +49,7 @@ def csvParse(csvStr, remap=None, merge=None):
|
||||||
reply = dict(total=0, valid=0)
|
reply = dict(total=0, valid=0)
|
||||||
results = []
|
results = []
|
||||||
try:
|
try:
|
||||||
headers = reader.next()
|
headers = next(reader)
|
||||||
fields = len(headers)
|
fields = len(headers)
|
||||||
except (StopIteration, csv.Error):
|
except (StopIteration, csv.Error):
|
||||||
return reply, {}
|
return reply, {}
|
||||||
|
@ -63,8 +65,7 @@ def csvParse(csvStr, remap=None, merge=None):
|
||||||
reply['total'] += 1
|
reply['total'] += 1
|
||||||
if len(row) != fields:
|
if len(row) != fields:
|
||||||
continue
|
continue
|
||||||
row = [unicode(cell, 'utf-8', 'replace') for cell in row]
|
values = dict(zip(headers, row))
|
||||||
values = dict(map(None, headers, row))
|
|
||||||
values.update(merge)
|
values.update(merge)
|
||||||
results.append(values)
|
results.append(values)
|
||||||
reply['valid'] += 1
|
reply['valid'] += 1
|
||||||
|
@ -88,19 +89,25 @@ def hash_password(password, salt=None):
|
||||||
:rtype: str"""
|
:rtype: str"""
|
||||||
if salt is None:
|
if salt is None:
|
||||||
salt_pool = string.ascii_letters + string.digits
|
salt_pool = string.ascii_letters + string.digits
|
||||||
salt = ''.join(random.choice(salt_pool) for x in xrange(32))
|
salt = ''.join(random.choice(salt_pool) for x in range(32))
|
||||||
hash_ = hashlib.sha512('%s%s' % (salt, password))
|
pwd = '%s%s' % (salt, password)
|
||||||
|
hash_ = hashlib.sha512(pwd.encode('utf-8'))
|
||||||
return '$%s$%s' % (salt, hash_.hexdigest())
|
return '$%s$%s' % (salt, hash_.hexdigest())
|
||||||
|
|
||||||
|
|
||||||
class ImprovedEncoder(json.JSONEncoder):
|
class ImprovedEncoder(json.JSONEncoder):
|
||||||
"""Enhance the default JSON encoder to serialize datetime and ObjectId instances."""
|
"""Enhance the default JSON encoder to serialize datetime and ObjectId instances."""
|
||||||
def default(self, o):
|
def default(self, o):
|
||||||
if isinstance(o, (datetime.datetime, datetime.date,
|
if isinstance(o, bytes):
|
||||||
|
try:
|
||||||
|
return o.decode('utf-8')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
elif isinstance(o, (datetime.datetime, datetime.date,
|
||||||
datetime.time, datetime.timedelta, ObjectId)):
|
datetime.time, datetime.timedelta, ObjectId)):
|
||||||
try:
|
try:
|
||||||
return str(o)
|
return str(o)
|
||||||
except Exception, e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
elif isinstance(o, set):
|
elif isinstance(o, set):
|
||||||
return list(o)
|
return list(o)
|
||||||
|
|
Loading…
Reference in a new issue