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
|
||||
===================
|
||||
|
||||
- [Python 3](https://www.python.org/) for the backend
|
||||
- [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
|
||||
- [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
|
||||
===============
|
||||
|
||||
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
|
||||
sudo python get-pip.py
|
||||
sudo pip install tornado # version 4.2 or later
|
||||
sudo pip install pymongo # version 3.2.2 or later
|
||||
sudo pip install python-dateutil
|
||||
sudo pip install pycups # only needed if you want to print labels
|
||||
sudo python3 get-pip.py
|
||||
sudo pip3 install tornado # version 4.2 or later
|
||||
sudo pip3 install pymongo # version 3.2.2 or later
|
||||
sudo pip3 install python-dateutil
|
||||
sudo pip3 install pycups # only needed if you want to print labels
|
||||
git clone https://github.com/raspibo/eventman
|
||||
cd eventman
|
||||
./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
|
||||
=====================
|
||||
|
||||
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");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -27,9 +27,12 @@
|
|||
<form class="form-inline">
|
||||
<div class="form-group">
|
||||
<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>
|
||||
</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">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -43,7 +46,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<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>
|
||||
<span>
|
||||
|
@ -71,6 +74,9 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
</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>
|
||||
|
@ -87,7 +93,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<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>
|
||||
<strong>{{person.name}} {{person.surname}}</strong>
|
||||
<br />
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<form class="form-inline">
|
||||
<div class="form-group">
|
||||
<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 class="form-group">
|
||||
<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-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-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-loader-static-files.min.js"></script>
|
||||
<script type="text/javascript" src="/static/js/nya-bs-select.min.js"></script>
|
||||
|
@ -52,7 +52,7 @@
|
|||
</head>
|
||||
|
||||
<!--
|
||||
Copyright 2015-2016 Davide Alberani <da@erlug.linux.it>
|
||||
Copyright 2015-2017 Davide Alberani <da@erlug.linux.it>
|
||||
RaspiBO <info@raspibo.org>
|
||||
|
||||
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';
|
||||
/*
|
||||
Copyright 2015-2016 Davide Alberani <da@erlug.linux.it>
|
||||
Copyright 2015-2017 Davide Alberani <da@erlug.linux.it>
|
||||
RaspiBO <info@raspibo.org>
|
||||
|
||||
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',
|
||||
function ($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, $filter) {
|
||||
$scope.query = '';
|
||||
$scope.tickets = [];
|
||||
$scope.events = Event.all(function(events) {
|
||||
if (events && $state.is('tickets')) {
|
||||
|
@ -79,11 +80,38 @@ eventManControllers.controller('EventsListCtrl', ['$scope', 'Event', '$uibModal'
|
|||
});
|
||||
$scope.tickets.push.apply($scope.tickets, evt_tickets || []);
|
||||
});
|
||||
$scope.filterTickets();
|
||||
}
|
||||
});
|
||||
$scope.eventsOrderProp = "-begin_date";
|
||||
$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?';
|
||||
$rootScope.$on('$translateChangeSuccess', function () {
|
||||
$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.filterTickets();
|
||||
};
|
||||
|
||||
}]
|
||||
);
|
||||
|
||||
|
@ -166,13 +194,15 @@ eventManControllers.controller('EventDetailsCtrl', ['$scope', '$state', 'Event',
|
|||
);
|
||||
|
||||
|
||||
eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event', 'EventTicket', 'Setting', '$log', '$translate', '$rootScope', 'EventUpdates', '$uibModal',
|
||||
function ($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, $filter) {
|
||||
$scope.ticketsOrder = ["name", "surname"];
|
||||
$scope.countAttendees = 0;
|
||||
$scope.message = {};
|
||||
$scope.query = '';
|
||||
$scope.event = {};
|
||||
$scope.event.tickets = [];
|
||||
$scope.shownItems = [];
|
||||
$scope.ticket = {}; // current ticket, for the event.ticket.* states
|
||||
$scope.tickets = []; // list of all tickets, for the 'tickets' state
|
||||
$scope.formSchema = {};
|
||||
|
@ -180,16 +210,47 @@ eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event',
|
|||
$scope.guiOptions = {dangerousActionsEnabled: false};
|
||||
$scope.customFields = Setting.query({setting: 'ticket_custom_field', in_event_details: true});
|
||||
$scope.registeredFilterOptions = {all: false};
|
||||
|
||||
$scope.formFieldsMap = {};
|
||||
$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) {
|
||||
$scope.event = Event.get({id: $state.params.id}, function(data) {
|
||||
$scope.$watchCollection(function() {
|
||||
return $scope.event.tickets;
|
||||
}, function(new_collection, old_collection) {
|
||||
$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 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;
|
||||
} else if (data.action == 'add' && ticket_idx == -1) {
|
||||
$scope._localAddTicket(data.ticket);
|
||||
|
@ -406,20 +473,24 @@ eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event',
|
|||
}
|
||||
|
||||
if (key === 'attended' && !hideMessage) {
|
||||
var msg = {};
|
||||
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(data.ticket, value);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$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.setTicketAttribute(ticket, key, value);
|
||||
$scope.query = '';
|
||||
|
@ -575,6 +646,7 @@ eventManControllers.controller('EventTicketsCtrl', ['$scope', '$state', 'Event',
|
|||
}
|
||||
);
|
||||
$scope.ticketsOrder = new_order;
|
||||
$scope.filterTickets();
|
||||
};
|
||||
|
||||
$scope.showMessage = function(cfg) {
|
||||
|
@ -649,7 +721,7 @@ eventManControllers.controller('UsersCtrl', ['$scope', '$rootScope', '$state', '
|
|||
User.login(loginData, function(data) {
|
||||
if (!data.error) {
|
||||
$rootScope.readInfo(function(info) {
|
||||
$log.debug('logged in user: ' + info.user.username);
|
||||
$log.debug('logged in user: ' + $scope.info.user.username);
|
||||
$rootScope.clearError();
|
||||
$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. */
|
||||
eventManApp.filter('attendeesFilter', ['$filter',
|
||||
function($filter) {
|
||||
|
|
|
@ -11,10 +11,13 @@
|
|||
<form class="form-inline">
|
||||
<div class="form-group">
|
||||
<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>
|
||||
</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">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -25,7 +28,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<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>
|
||||
<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>
|
||||
</tbody>
|
||||
</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>
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<form class="form-inline">
|
||||
<div class="form-group">
|
||||
<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 class="form-group">
|
||||
<label for="tickets-order">{{'Sort by:' | translate}}</label>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<form class="form-inline">
|
||||
<div class="form-group">
|
||||
<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 class="form-group">
|
||||
<label for="users-order">Sort by:</label>
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
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.
|
||||
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
"""EventMan(ager)
|
||||
|
||||
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>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
@ -38,7 +38,8 @@ import tornado.websocket
|
|||
from tornado import gen, escape, process
|
||||
|
||||
import utils
|
||||
import backend
|
||||
import monco
|
||||
import collections
|
||||
|
||||
ENCODING = 'utf-8'
|
||||
PROCESS_TIMEOUT = 60
|
||||
|
@ -102,8 +103,8 @@ class BaseHandler(tornado.web.RequestHandler):
|
|||
_users_cache = {}
|
||||
|
||||
# A property to access the first value of each argument.
|
||||
arguments = property(lambda self: dict([(k, v[0])
|
||||
for k, v in self.request.arguments.iteritems()]))
|
||||
arguments = property(lambda self: dict([(k, v[0].decode('utf-8'))
|
||||
for k, v in self.request.arguments.items()]))
|
||||
|
||||
# A property to access both the UUID and the clean arguments.
|
||||
@property
|
||||
|
@ -150,23 +151,26 @@ class BaseHandler(tornado.web.RequestHandler):
|
|||
"""Convert some textual values to boolean."""
|
||||
if isinstance(obj, (list, tuple)):
|
||||
obj = obj[0]
|
||||
if isinstance(obj, (str, unicode)):
|
||||
if isinstance(obj, str):
|
||||
obj = obj.lower()
|
||||
return self._bool_convert.get(obj, obj)
|
||||
|
||||
def arguments_tobool(self):
|
||||
"""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):
|
||||
"""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)
|
||||
|
||||
@property
|
||||
def current_user(self):
|
||||
"""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
|
||||
def current_user_info(self):
|
||||
|
@ -174,16 +178,16 @@ class BaseHandler(tornado.web.RequestHandler):
|
|||
current_user = self.current_user
|
||||
if current_user in self._users_cache:
|
||||
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}
|
||||
if current_user:
|
||||
user_info['username'] = current_user
|
||||
res = self.db.query('users', {'username': current_user})
|
||||
if res:
|
||||
user = res[0]
|
||||
user_info['_id'] = current_user
|
||||
user = self.db.getOne('users', {'_id': current_user})
|
||||
if user:
|
||||
user_info = user
|
||||
permissions.update(set(user.get('permissions') or []))
|
||||
user_info['permissions'] = permissions
|
||||
user_info['isRegistered'] = True
|
||||
self._users_cache[current_user] = user_info
|
||||
return user_info
|
||||
|
||||
|
@ -204,7 +208,7 @@ class BaseHandler(tornado.web.RequestHandler):
|
|||
collection_permission = self.permissions.get(permission)
|
||||
if isinstance(collection_permission, bool):
|
||||
return collection_permission
|
||||
if callable(collection_permission):
|
||||
if isinstance(collection_permission, collections.Callable):
|
||||
return collection_permission(permission)
|
||||
return False
|
||||
|
||||
|
@ -305,7 +309,7 @@ class CollectionHandler(BaseHandler):
|
|||
:rtype: str"""
|
||||
t = str(time.time()).replace('.', '_')
|
||||
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))
|
||||
|
||||
def _filter_results(self, results, params):
|
||||
|
@ -320,11 +324,11 @@ class CollectionHandler(BaseHandler):
|
|||
:rtype: list"""
|
||||
if not params:
|
||||
return results
|
||||
params = backend.convert(params)
|
||||
params = monco.convert(params)
|
||||
filtered = []
|
||||
for result in results:
|
||||
add = True
|
||||
for key, value in params.iteritems():
|
||||
for key, value in params.items():
|
||||
if key not in result or result[key] != value:
|
||||
add = False
|
||||
break
|
||||
|
@ -338,8 +342,8 @@ class CollectionHandler(BaseHandler):
|
|||
:param data: dictionary to clean
|
||||
:type data: dict"""
|
||||
if isinstance(data, dict):
|
||||
for key in data.keys():
|
||||
if isinstance(key, (str, unicode)) and key.startswith('$'):
|
||||
for key in list(data.keys()):
|
||||
if isinstance(key, str) and key.startswith('$'):
|
||||
del data[key]
|
||||
return data
|
||||
|
||||
|
@ -349,7 +353,7 @@ class CollectionHandler(BaseHandler):
|
|||
:param data: dictionary to convert
|
||||
:type data: dict"""
|
||||
ret = {}
|
||||
for key, value in data.iteritems():
|
||||
for key, value in data.items():
|
||||
if isinstance(value, (list, tuple, dict)):
|
||||
continue
|
||||
try:
|
||||
|
@ -357,7 +361,7 @@ class CollectionHandler(BaseHandler):
|
|||
key = re_env_key.sub('', key)
|
||||
if not key:
|
||||
continue
|
||||
ret[key] = unicode(value).encode(ENCODING)
|
||||
ret[key] = str(value).encode(ENCODING)
|
||||
except:
|
||||
continue
|
||||
return ret
|
||||
|
@ -382,7 +386,7 @@ class CollectionHandler(BaseHandler):
|
|||
if acl and not self.has_permission(permission):
|
||||
return self.build_error(status=401, message='insufficient permissions: %s' % permission)
|
||||
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 = self.apply_filter(output, 'get_%s' % resource)
|
||||
self.write(output)
|
||||
|
@ -432,7 +436,7 @@ class CollectionHandler(BaseHandler):
|
|||
return self.build_error(status=401, message='insufficient permissions: %s' % permission)
|
||||
# Handle access to sub-resources.
|
||||
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))
|
||||
output = handler(id_, resource_id, data, **kwargs)
|
||||
output = self.apply_filter(output, 'get_%s' % resource)
|
||||
|
@ -478,7 +482,7 @@ class CollectionHandler(BaseHandler):
|
|||
if not self.has_permission(permission):
|
||||
return self.build_error(status=401, message='insufficient permissions: %s' % permission)
|
||||
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)
|
||||
env['RESOURCE'] = resource
|
||||
if resource_id:
|
||||
|
@ -584,7 +588,7 @@ class CollectionHandler(BaseHandler):
|
|||
ws = yield tornado.websocket.websocket_connect(self.build_ws_url(path))
|
||||
ws.write_message(message)
|
||||
ws.close()
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
self.logger.error('Error yielding WebSocket message: %s', e)
|
||||
|
||||
|
||||
|
@ -641,7 +645,7 @@ class EventsHandler(CollectionHandler):
|
|||
if group_id is None:
|
||||
return {'persons': persons}
|
||||
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}
|
||||
events = self.db.query('events', all_query)
|
||||
for event in events:
|
||||
|
@ -655,7 +659,7 @@ class EventsHandler(CollectionHandler):
|
|||
or which set of keys specified in a dictionary match their respective values."""
|
||||
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.iteritems()):
|
||||
if all(ticket.get(k) == v for k, v in ticket_id_or_query.items()):
|
||||
return ticket
|
||||
else:
|
||||
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 [])
|
||||
env = dict(ticket)
|
||||
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})
|
||||
stdin_data = {'new': ticket,
|
||||
'event': doc,
|
||||
|
@ -765,7 +769,7 @@ class EventsHandler(CollectionHandler):
|
|||
# Update an existing entry for a ticket registered at this event.
|
||||
self._clean_dict(data)
|
||||
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_
|
||||
if ticket_id is not None:
|
||||
query['tickets._id'] = ticket_id
|
||||
|
@ -794,7 +798,7 @@ class EventsHandler(CollectionHandler):
|
|||
# always takes the ticket_id from the new ticket
|
||||
ticket_id = str(new_ticket_data.get('_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})
|
||||
stdin_data = {'old': old_ticket_data,
|
||||
'new': new_ticket_data,
|
||||
|
@ -806,7 +810,8 @@ class EventsHandler(CollectionHandler):
|
|||
if new_ticket_data.get('attended'):
|
||||
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:
|
||||
self.send_ws_message('event/%s/tickets/updates' % id_, json.dumps(ret))
|
||||
return ret
|
||||
|
@ -827,7 +832,7 @@ class EventsHandler(CollectionHandler):
|
|||
self.send_ws_message('event/%s/tickets/updates' % id_, json.dumps(ret))
|
||||
env = dict(ticket)
|
||||
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})
|
||||
stdin_data = {'old': ticket,
|
||||
'event': rdoc,
|
||||
|
@ -872,7 +877,7 @@ class UsersHandler(CollectionHandler):
|
|||
@authenticated
|
||||
def get(self, id_=None, resource=None, resource_id=None, acl=True, **kwargs):
|
||||
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
|
||||
super(UsersHandler, self).get(id_, resource, resource_id, acl=acl, **kwargs)
|
||||
|
||||
|
@ -896,12 +901,17 @@ class UsersHandler(CollectionHandler):
|
|||
if new_pwd is not None:
|
||||
del data['new_password']
|
||||
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')
|
||||
data['password'] = utils.hash_password(new_pwd)
|
||||
if '_id' in data:
|
||||
# Avoid overriding _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
|
||||
|
||||
@gen.coroutine
|
||||
|
@ -909,7 +919,7 @@ class UsersHandler(CollectionHandler):
|
|||
def put(self, id_=None, resource=None, resource_id=None, **kwargs):
|
||||
if id_ is None:
|
||||
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')
|
||||
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')])
|
||||
for ticket in (event_details[0].get('tickets') or []):
|
||||
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:
|
||||
filename = content['filename']
|
||||
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)
|
||||
count = 0
|
||||
_to_delete = set()
|
||||
for uuid, client in _ws_clients.get(url, {}).iteritems():
|
||||
for uuid, client in _ws_clients.get(url, {}).items():
|
||||
try:
|
||||
client.write_message(message)
|
||||
except:
|
||||
|
@ -1079,10 +1089,11 @@ class LoginHandler(RootHandler):
|
|||
self.write({'error': True, 'message': 'missing username or password'})
|
||||
return
|
||||
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']
|
||||
logging.info('successful login for user %s' % username)
|
||||
self.set_secure_cookie("user", username)
|
||||
logging.info('successful login for user %s (id: %s)' % (username, id_))
|
||||
self.set_secure_cookie("user", id_)
|
||||
self.write({'error': False, 'message': 'successful login'})
|
||||
return
|
||||
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)
|
||||
|
||||
# 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,
|
||||
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"),
|
||||
static_path=os.path.join(os.path.dirname(__file__), "static"),
|
||||
cookie_secret='__COOKIE_SECRET__',
|
||||
cookie_secret=cookie_secret,
|
||||
login_url='/login',
|
||||
debug=options.debug)
|
||||
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>
|
||||
|
||||
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}')
|
||||
|
||||
_force_conversion = {
|
||||
'_id': ObjectId,
|
||||
'seq_hex': str,
|
||||
'tickets.seq_hex': str
|
||||
}
|
||||
|
@ -40,7 +41,8 @@ def convert_obj(obj):
|
|||
if isinstance(obj, bool):
|
||||
return obj
|
||||
try:
|
||||
return ObjectId(obj)
|
||||
if re_objectid.match(obj):
|
||||
return ObjectId(obj)
|
||||
except:
|
||||
pass
|
||||
return obj
|
||||
|
@ -56,9 +58,12 @@ def convert(seq):
|
|||
"""
|
||||
if isinstance(seq, dict):
|
||||
d = {}
|
||||
for key, item in seq.iteritems():
|
||||
for key, item in seq.items():
|
||||
if key in _force_conversion:
|
||||
d[key] = _force_conversion[key](item)
|
||||
try:
|
||||
d[key] = _force_conversion[key](item)
|
||||
except:
|
||||
d[key] = item
|
||||
else:
|
||||
d[key] = convert(item)
|
||||
return d
|
||||
|
@ -67,23 +72,35 @@ def convert(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."""
|
||||
db = None
|
||||
connection = None
|
||||
|
||||
# map operations on lists of items.
|
||||
_operations = {
|
||||
'update': '$set',
|
||||
'append': '$push',
|
||||
'appendUnique': '$addToSet',
|
||||
'delete': '$pull',
|
||||
'increment': '$inc'
|
||||
'update': '$set',
|
||||
'append': '$push',
|
||||
'appendUnique': '$addToSet',
|
||||
'delete': '$pull',
|
||||
'increment': '$inc'
|
||||
}
|
||||
|
||||
def __init__(self, url=None, dbName='eventman'):
|
||||
def __init__(self, dbName, url=None):
|
||||
"""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
|
||||
:type url: str (or None to connect to localhost)
|
||||
"""
|
||||
|
@ -91,9 +108,11 @@ class EventManDB(object):
|
|||
self._dbName = dbName
|
||||
self.connect(url)
|
||||
|
||||
def connect(self, url=None, dbName=None):
|
||||
def connect(self, dbName=None, url=None):
|
||||
"""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
|
||||
:type url: str (or None to connect to localhost)
|
||||
|
||||
|
@ -106,10 +125,26 @@ class EventManDB(object):
|
|||
self._url = url
|
||||
if dbName:
|
||||
self._dbName = dbName
|
||||
if not self._dbName:
|
||||
raise MoncoConnectionError('no database name specified')
|
||||
self.connection = pymongo.MongoClient(self._url)
|
||||
self.db = self.connection[self._dbName]
|
||||
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):
|
||||
"""Get a single document with the specified `_id`.
|
||||
|
||||
|
@ -121,8 +156,7 @@ class EventManDB(object):
|
|||
:returns: the document with the given `_id`
|
||||
:rtype: dict
|
||||
"""
|
||||
results = self.query(collection, convert({'_id': _id}))
|
||||
return results and results[0] or {}
|
||||
return self.getOne(collection, {'_id': _id})
|
||||
|
||||
def query(self, collection, query=None, condition='or'):
|
||||
"""Get multiple documents matching a query.
|
||||
|
@ -130,7 +164,7 @@ class EventManDB(object):
|
|||
:param collection: search for documents in this collection
|
||||
:type collection: str
|
||||
:param query: search for documents with those attributes
|
||||
:type query: dict or None
|
||||
:type query: dict, list or None
|
||||
|
||||
:returns: list of matching documents
|
||||
:rtype: list
|
||||
|
@ -222,7 +256,7 @@ class EventManDB(object):
|
|||
operator = self._operations.get(operation)
|
||||
if updateList:
|
||||
newData = {}
|
||||
for key, value in data.iteritems():
|
||||
for key, value in data.items():
|
||||
newData['%s.$.%s' % (updateList, key)] = value
|
||||
data = newData
|
||||
res = db[collection].find_and_modify(query=_id_or_query,
|
||||
|
@ -230,6 +264,30 @@ class EventManDB(object):
|
|||
lastErrorObject = res.get('lastErrorObject') 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):
|
||||
"""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
|
||||
:type force: bool
|
||||
|
||||
:returns: how many documents were removed
|
||||
:rtype: int
|
||||
:returns: dictionary with the number or removed documents
|
||||
:rtype: dict
|
||||
"""
|
||||
if not _id_or_query and not force:
|
||||
return
|
||||
|
@ -250,4 +308,3 @@ class EventManDB(object):
|
|||
_id_or_query = {'_id': _id_or_query}
|
||||
_id_or_query = convert(_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';
|
||||
|
||||
var noop = angular.noop;
|
||||
var objectFreeze = (Object.freeze) ? Object.freeze : noop;
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
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 isString = angular.isString;
|
||||
var isFunction = angular.isFunction;
|
||||
var isDefined = angular.isDefined;
|
||||
var isObject = angular.isObject;
|
||||
var isArray = angular.isArray;
|
||||
var forEach = angular.forEach;
|
||||
var isString = _angular2.default.isString;
|
||||
var isFunction = _angular2.default.isFunction;
|
||||
var isDefined = _angular2.default.isDefined;
|
||||
var isObject = _angular2.default.isObject;
|
||||
var isArray = _angular2.default.isArray;
|
||||
var forEach = _angular2.default.forEach;
|
||||
var arraySlice = Array.prototype.slice;
|
||||
// ie8 wat
|
||||
if (!Array.prototype.indexOf) {
|
||||
Array.prototype.indexOf = function(elt /*, from*/) {
|
||||
Array.prototype.indexOf = function (elt /*, from*/) {
|
||||
var len = this.length >>> 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) {
|
||||
from += len;
|
||||
}
|
||||
|
||||
for (; from < len; from++) {
|
||||
if (from in this && this[from] === elt) { return from; }
|
||||
if (from in this && this[from] === elt) {
|
||||
return from;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
@ -48,20 +92,20 @@
|
|||
// this.buffer = [];
|
||||
|
||||
// TODO: refactor options to use isDefined
|
||||
this.scope = options && options.scope || $rootScope;
|
||||
this.rootScopeFailover = options && options.rootScopeFailover && true;
|
||||
this.useApplyAsync = options && options.useApplyAsync || false;
|
||||
this.initialTimeout = options && options.initialTimeout || 500; // 500ms
|
||||
this.maxTimeout = options && options.maxTimeout || 5 * 60 * 1000; // 5 minutes
|
||||
this.reconnectIfNotNormalClose = options && options.reconnectIfNotNormalClose || false;
|
||||
this.binaryType = options && options.binaryType || 'blob';
|
||||
this.scope = options && options.scope || $rootScope;
|
||||
this.rootScopeFailover = options && options.rootScopeFailover && true;
|
||||
this.useApplyAsync = options && options.useApplyAsync || false;
|
||||
this.initialTimeout = options && options.initialTimeout || 500; // 500ms
|
||||
this.maxTimeout = options && options.maxTimeout || 5 * 60 * 1000; // 5 minutes
|
||||
this.reconnectIfNotNormalClose = options && options.reconnectIfNotNormalClose || false;
|
||||
this.binaryType = options && options.binaryType || 'blob';
|
||||
|
||||
this._reconnectAttempts = 0;
|
||||
this.sendQueue = [];
|
||||
this.onOpenCallbacks = [];
|
||||
this.sendQueue = [];
|
||||
this.onOpenCallbacks = [];
|
||||
this.onMessageCallbacks = [];
|
||||
this.onErrorCallbacks = [];
|
||||
this.onCloseCallbacks = [];
|
||||
this.onErrorCallbacks = [];
|
||||
this.onCloseCallbacks = [];
|
||||
|
||||
objectFreeze(this._readyStateConstants);
|
||||
|
||||
|
@ -70,10 +114,8 @@
|
|||
} else {
|
||||
this._setInternalState(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
$WebSocket.prototype._readyStateConstants = {
|
||||
'CONNECTING': 0,
|
||||
'OPEN': 1,
|
||||
|
@ -84,9 +126,7 @@
|
|||
|
||||
$WebSocket.prototype._normalCloseCode = 1000;
|
||||
|
||||
$WebSocket.prototype._reconnectableStatusCodes = [
|
||||
4000
|
||||
];
|
||||
$WebSocket.prototype._reconnectableStatusCodes = [4000];
|
||||
|
||||
$WebSocket.prototype.safeDigest = function safeDigest(autoApply) {
|
||||
if (autoApply && !this.scope.$$phase) {
|
||||
|
@ -99,7 +139,7 @@
|
|||
if (scope) {
|
||||
this.scope = scope;
|
||||
if (this.rootScopeFailover) {
|
||||
this.scope.$on('$destroy', function() {
|
||||
this.scope.$on('$destroy', function () {
|
||||
self.scope = $rootScope;
|
||||
});
|
||||
}
|
||||
|
@ -110,10 +150,10 @@
|
|||
$WebSocket.prototype._connect = function _connect(force) {
|
||||
if (force || !this.socket || this.socket.readyState !== this._readyStateConstants.OPEN) {
|
||||
this.socket = $websocketBackend.create(this.url, this.protocols);
|
||||
this.socket.onmessage = angular.bind(this, this._onMessageHandler);
|
||||
this.socket.onopen = angular.bind(this, this._onOpenHandler);
|
||||
this.socket.onerror = angular.bind(this, this._onErrorHandler);
|
||||
this.socket.onclose = angular.bind(this, this._onCloseHandler);
|
||||
this.socket.onmessage = _angular2.default.bind(this, this._onMessageHandler);
|
||||
this.socket.onopen = _angular2.default.bind(this, this._onOpenHandler);
|
||||
this.socket.onerror = _angular2.default.bind(this, this._onErrorHandler);
|
||||
this.socket.onclose = _angular2.default.bind(this, this._onCloseHandler);
|
||||
this.socket.binaryType = this.binaryType;
|
||||
}
|
||||
};
|
||||
|
@ -122,9 +162,7 @@
|
|||
while (this.sendQueue.length && this.socket.readyState === this._readyStateConstants.OPEN) {
|
||||
var data = this.sendQueue.shift();
|
||||
|
||||
this.socket.send(
|
||||
isString(data.message) || this.binaryType != "blob" ? data.message : JSON.stringify(data.message)
|
||||
);
|
||||
this.socket.send(isString(data.message) || this.binaryType != 'blob' ? data.message : JSON.stringify(data.message));
|
||||
data.deferred.resolve();
|
||||
}
|
||||
};
|
||||
|
@ -162,7 +200,6 @@
|
|||
return this;
|
||||
};
|
||||
|
||||
|
||||
$WebSocket.prototype.onMessage = function onMessage(callback, options) {
|
||||
if (!isFunction(callback)) {
|
||||
throw new Error('Callback must be a function');
|
||||
|
@ -189,14 +226,14 @@
|
|||
$WebSocket.prototype._onCloseHandler = function _onCloseHandler(event) {
|
||||
var self = this;
|
||||
if (self.useApplyAsync) {
|
||||
self.scope.$applyAsync(function() {
|
||||
self.scope.$applyAsync(function () {
|
||||
self.notifyCloseCallbacks(event);
|
||||
});
|
||||
} else {
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
@ -204,12 +241,12 @@
|
|||
$WebSocket.prototype._onErrorHandler = function _onErrorHandler(event) {
|
||||
var self = this;
|
||||
if (self.useApplyAsync) {
|
||||
self.scope.$applyAsync(function() {
|
||||
self.scope.$applyAsync(function () {
|
||||
self.notifyErrorCallbacks(event);
|
||||
});
|
||||
} else {
|
||||
self.notifyErrorCallbacks(event);
|
||||
self.safeDigest(autoApply);
|
||||
self.safeDigest(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -223,12 +260,10 @@
|
|||
if (pattern) {
|
||||
if (isString(pattern) && message.data === pattern) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message);
|
||||
}
|
||||
}
|
||||
|
@ -236,7 +271,7 @@
|
|||
function applyAsyncOrDigest(callback, autoApply, args) {
|
||||
args = arraySlice.call(arguments, 2);
|
||||
if (self.useApplyAsync) {
|
||||
self.scope.$applyAsync(function() {
|
||||
self.scope.$applyAsync(function () {
|
||||
callback.apply(self, args);
|
||||
});
|
||||
} else {
|
||||
|
@ -244,7 +279,6 @@
|
|||
self.safeDigest(autoApply);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$WebSocket.prototype.close = function close(force) {
|
||||
|
@ -261,8 +295,7 @@
|
|||
|
||||
if (self.readyState === self._readyStateConstants.RECONNECT_ABORTED) {
|
||||
deferred.reject('Socket connection has been closed');
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
self.sendQueue.push({
|
||||
message: data,
|
||||
deferred: deferred
|
||||
|
@ -274,7 +307,7 @@
|
|||
function cancelableify(promise) {
|
||||
promise.cancel = cancel;
|
||||
var then = promise.then;
|
||||
promise.then = function() {
|
||||
promise.then = function () {
|
||||
var newPromise = then.apply(this, arguments);
|
||||
return cancelableify(newPromise);
|
||||
};
|
||||
|
@ -287,8 +320,7 @@
|
|||
return self;
|
||||
}
|
||||
|
||||
if ($websocketBackend.isMocked && $websocketBackend.isMocked() &&
|
||||
$websocketBackend.isConnected(this.url)) {
|
||||
if ($websocketBackend.isMocked && $websocketBackend.isMocked() && $websocketBackend.isConnected(this.url)) {
|
||||
this._onMessageHandler($websocketBackend.mockSend());
|
||||
}
|
||||
|
||||
|
@ -303,7 +335,7 @@
|
|||
var backoffDelaySeconds = backoffDelay / 1000;
|
||||
console.log('Reconnecting in ' + backoffDelaySeconds + ' seconds');
|
||||
|
||||
$timeout(angular.bind(this, this._connect), backoffDelay);
|
||||
$timeout(_angular2.default.bind(this, this._connect), backoffDelay);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
@ -330,8 +362,7 @@
|
|||
}
|
||||
this._internalConnectionState = state;
|
||||
|
||||
|
||||
forEach(this.sendQueue, function(pending) {
|
||||
forEach(this.sendQueue, function (pending) {
|
||||
pending.deferred.reject('Message cancelled due to closed socket connection');
|
||||
});
|
||||
};
|
||||
|
@ -339,62 +370,46 @@
|
|||
// Read only .readyState
|
||||
if (objectDefineProperty) {
|
||||
objectDefineProperty($WebSocket.prototype, 'readyState', {
|
||||
get: function() {
|
||||
get: function get() {
|
||||
return this._internalConnectionState || this.socket.readyState;
|
||||
},
|
||||
set: function() {
|
||||
set: function set() {
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
// $WebSocketBackendProvider.$inject = ['$window', '$log'];
|
||||
function $WebSocketBackendProvider($window, $log) {
|
||||
// $WebSocketBackendProvider.$inject = ['$log'];
|
||||
function $WebSocketBackendProvider($log) {
|
||||
this.create = function create(url, protocols) {
|
||||
var match = /wss?:\/\//.exec(url);
|
||||
var Socket, ws;
|
||||
|
||||
if (!match) {
|
||||
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) {
|
||||
return new Socket(url, protocols);
|
||||
}
|
||||
|
||||
return new Socket(url);
|
||||
};
|
||||
|
||||
this.createWebSocketBackend = function createWebSocketBackend(url, protocols) {
|
||||
$log.warn('Deprecated: Please use .create(url, protocols)');
|
||||
return this.create(url, protocols);
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('ngWebSocket', [])
|
||||
.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('ngWebSocket', []).factory('$websocket', ['$rootScope', '$q', '$timeout', '$websocketBackend', $WebSocketProvider]).factory('WebSocket', ['$rootScope', '$q', '$timeout', 'WebsocketBackend', $WebSocketProvider]).service('$websocketBackend', ['$log', $WebSocketBackendProvider]).service('WebSocketBackend', ['$log', $WebSocketBackendProvider]);
|
||||
|
||||
_angular2.default.module('angular-websocket', ['ngWebSocket']);
|
||||
|
||||
angular.module('angular-websocket', ['ngWebSocket']);
|
||||
|
||||
if (typeof module === 'object' && typeof define !== 'function') {
|
||||
module.exports = angular.module('ngWebSocket');
|
||||
}
|
||||
}());
|
||||
exports.default = _angular2.default.module('ngWebSocket');
|
||||
module.exports = exports['default'];
|
||||
});
|
||||
|
|
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 hashlib
|
||||
import datetime
|
||||
import StringIO
|
||||
import io
|
||||
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
|
||||
:rtype: tuple
|
||||
"""
|
||||
fd = StringIO.StringIO(csvStr)
|
||||
if isinstance(csvStr, bytes):
|
||||
csvStr = csvStr.decode('utf-8')
|
||||
fd = io.StringIO(csvStr)
|
||||
reader = csv.reader(fd)
|
||||
remap = remap or {}
|
||||
merge = merge or {}
|
||||
|
@ -47,7 +49,7 @@ def csvParse(csvStr, remap=None, merge=None):
|
|||
reply = dict(total=0, valid=0)
|
||||
results = []
|
||||
try:
|
||||
headers = reader.next()
|
||||
headers = next(reader)
|
||||
fields = len(headers)
|
||||
except (StopIteration, csv.Error):
|
||||
return reply, {}
|
||||
|
@ -63,8 +65,7 @@ def csvParse(csvStr, remap=None, merge=None):
|
|||
reply['total'] += 1
|
||||
if len(row) != fields:
|
||||
continue
|
||||
row = [unicode(cell, 'utf-8', 'replace') for cell in row]
|
||||
values = dict(map(None, headers, row))
|
||||
values = dict(zip(headers, row))
|
||||
values.update(merge)
|
||||
results.append(values)
|
||||
reply['valid'] += 1
|
||||
|
@ -88,19 +89,25 @@ def hash_password(password, salt=None):
|
|||
:rtype: str"""
|
||||
if salt is None:
|
||||
salt_pool = string.ascii_letters + string.digits
|
||||
salt = ''.join(random.choice(salt_pool) for x in xrange(32))
|
||||
hash_ = hashlib.sha512('%s%s' % (salt, password))
|
||||
salt = ''.join(random.choice(salt_pool) for x in range(32))
|
||||
pwd = '%s%s' % (salt, password)
|
||||
hash_ = hashlib.sha512(pwd.encode('utf-8'))
|
||||
return '$%s$%s' % (salt, hash_.hexdigest())
|
||||
|
||||
|
||||
class ImprovedEncoder(json.JSONEncoder):
|
||||
"""Enhance the default JSON encoder to serialize datetime and ObjectId instances."""
|
||||
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)):
|
||||
try:
|
||||
return str(o)
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
pass
|
||||
elif isinstance(o, set):
|
||||
return list(o)
|
||||
|
|
Loading…
Reference in a new issue