commit
821c6784e9
11 changed files with 282 additions and 92 deletions
62
README.md
62
README.md
|
@ -1,7 +1,7 @@
|
||||||
Event Man(ager)
|
Event Man(ager)
|
||||||
===============
|
===============
|
||||||
|
|
||||||
Manage attendants at an event.
|
Your friendly manager of attendees at an event.
|
||||||
|
|
||||||
Notice
|
Notice
|
||||||
======
|
======
|
||||||
|
@ -10,11 +10,50 @@ No, this project is not ready, yet.
|
||||||
|
|
||||||
I'll let you know when I'm finished experimenting with it and you can contribute.
|
I'll let you know when I'm finished experimenting with it and you can contribute.
|
||||||
|
|
||||||
|
See the DEVELOPMENT.md file for more information about how to contribute.
|
||||||
|
|
||||||
|
|
||||||
|
Technological stack
|
||||||
|
===================
|
||||||
|
|
||||||
|
- [AngularJS](https://angularjs.org/) for the webApp
|
||||||
|
- [Bootstrap](http://getbootstrap.com/) (plus [jQuery](https://jquery.com/)) for the eye-candy
|
||||||
|
- [Tornado web](http://www.tornadoweb.org/) as web server
|
||||||
|
- [MongoDB](https://www.mongodb.org/) to store the data
|
||||||
|
|
||||||
|
The web part is incuded; you need to install Tornado, MongoDB and the pymongo module on your system (no configuration needed).
|
||||||
|
|
||||||
|
|
||||||
|
Coding style and conventions
|
||||||
|
============================
|
||||||
|
|
||||||
|
It's enough to be consistent within the document you're editing.
|
||||||
|
|
||||||
|
I suggest four spaces instead of tabs for all the code: Python (**mandatory**), JavaScript, HTML and CSS.
|
||||||
|
|
||||||
|
Python code documented following the [Sphinx](http://sphinx-doc.org/) syntax.
|
||||||
|
|
||||||
|
|
||||||
|
Install and run
|
||||||
|
===============
|
||||||
|
|
||||||
|
wget https://bootstrap.pypa.io/get-pip.py
|
||||||
|
sudo python get-pip.py
|
||||||
|
sudo pip install tornado
|
||||||
|
sudo pip install pymongo
|
||||||
|
cd
|
||||||
|
git clone https://github.com/raspibo/eventman
|
||||||
|
cd eventman
|
||||||
|
./eventman_server.py --debug
|
||||||
|
|
||||||
|
|
||||||
|
Open browser and navigate to: http://localhost:5242/
|
||||||
|
|
||||||
|
|
||||||
License and copyright
|
License and copyright
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
Copyright 2015 Davide Alberani <da@erlug.linux.it>
|
Copyright 2015 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");
|
||||||
|
@ -27,22 +66,3 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
Install and run
|
|
||||||
===============
|
|
||||||
|
|
||||||
wget https://bootstrap.pypa.io/get-pip.py
|
|
||||||
|
|
||||||
sudo python get-pip.py
|
|
||||||
|
|
||||||
sudo pip install tornado
|
|
||||||
|
|
||||||
cd
|
|
||||||
|
|
||||||
git clone https://github.com/raspibo/eventman
|
|
||||||
|
|
||||||
cd eventman
|
|
||||||
|
|
||||||
./eventman_server.py
|
|
||||||
|
|
||||||
|
|
||||||
Open browser and navigate to: http://localhost:5242/
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
<!-- show details of a single Event (editing also take place here) -->
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<form ng-model="eventdetails" ng-submit="save()">
|
<form ng-model="eventdetails" ng-submit="save()">
|
||||||
<div class="input-group input-group-lg">
|
<div class="input-group input-group-lg">
|
||||||
|
|
|
@ -1,28 +1,25 @@
|
||||||
<div class="container-fluid">
|
<!-- show a list of Events -->
|
||||||
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
|
Search: <input ng-model="query">
|
||||||
Search: <input ng-model="query">
|
Sort by:
|
||||||
Sort by:
|
<select ng-model="orderProp">
|
||||||
<select ng-model="orderProp">
|
<option value="title">Alphabetical</option>
|
||||||
<option value="title">Alphabetical</option>
|
<option value="begin-datetime">Date</option>
|
||||||
<option value="begin-datetime">Date</option>
|
</select>
|
||||||
</select>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
|
<ul class="events">
|
||||||
<ul class="events">
|
<li ng-repeat="event in events | filter:query | orderBy:orderProp">
|
||||||
<li ng-repeat="event in events | filter:query | orderBy:orderProp">
|
<span><a href="/#/events/{{event._id}}">{{event.title}}</a></span>
|
||||||
<span><a href="/#/events/{{event._id}}">{{event.title}}</a></span>
|
<p>{{event['begin-datetime']}}</p>
|
||||||
<p>{{event['begin-datetime']}}</p>
|
</li>
|
||||||
</li>
|
</ul>
|
||||||
</ul>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html ng-app="eventManApp">
|
<html ng-app="eventManApp">
|
||||||
<head>
|
<head>
|
||||||
|
<title>Event Man(ager)</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
@ -10,11 +11,25 @@
|
||||||
<script src="/js/app.js"></script>
|
<script src="/js/app.js"></script>
|
||||||
<script src="/js/services.js"></script>
|
<script src="/js/services.js"></script>
|
||||||
<script src="/js/controllers.js"></script>
|
<script src="/js/controllers.js"></script>
|
||||||
<title>Event Man(ager)</title>
|
|
||||||
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
|
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link href="/static/css/bootstrap-theme.min.css" rel="stylesheet">
|
<link href="/static/css/bootstrap-theme.min.css" rel="stylesheet">
|
||||||
<link href="/static/css/eventman.css" rel="stylesheet">
|
<link href="/static/css/eventman.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Copyright 2015 Davide Alberani <da@erlug.linux.it>
|
||||||
|
RaspiBO <info@raspibo.org>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="main-header" ng-controller="navigation as n">
|
<div class="main-header" ng-controller="navigation as n">
|
||||||
|
@ -24,6 +39,8 @@
|
||||||
<input type="button" id="persons-button" ng-click="n.go('/new-person')" class="btn btn-link" value="Add Persons" />
|
<input type="button" id="persons-button" ng-click="n.go('/new-person')" class="btn btn-link" value="Add Persons" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- all the magic takes place here: the content inside the next div
|
||||||
|
changes accordingly to the location you're visiting -->
|
||||||
<div ng-view></div>
|
<div ng-view></div>
|
||||||
|
|
||||||
<div class="main-footer">
|
<div class="main-footer">
|
||||||
|
|
23
angular_app/js/app.js
vendored
23
angular_app/js/app.js
vendored
|
@ -1,3 +1,21 @@
|
||||||
|
'use strict';
|
||||||
|
/*
|
||||||
|
Copyright 2015 Davide Alberani <da@erlug.linux.it>
|
||||||
|
RaspiBO <info@raspibo.org>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/* Register our fantastic app. */
|
||||||
var eventManApp = angular.module('eventManApp', [
|
var eventManApp = angular.module('eventManApp', [
|
||||||
'ngRoute',
|
'ngRoute',
|
||||||
'eventManServices',
|
'eventManServices',
|
||||||
|
@ -5,6 +23,7 @@ var eventManApp = angular.module('eventManApp', [
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
/* Directive that can be used to make an input field react to the press of Enter. */
|
||||||
eventManApp.directive('ngEnter', function () {
|
eventManApp.directive('ngEnter', function () {
|
||||||
return function (scope, element, attrs) {
|
return function (scope, element, attrs) {
|
||||||
element.bind("keydown keypress", function (event) {
|
element.bind("keydown keypress", function (event) {
|
||||||
|
@ -19,6 +38,7 @@ eventManApp.directive('ngEnter', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/* Configure the routes. */
|
||||||
eventManApp.config(['$routeProvider',
|
eventManApp.config(['$routeProvider',
|
||||||
function($routeProvider) {
|
function($routeProvider) {
|
||||||
$routeProvider.
|
$routeProvider.
|
||||||
|
@ -49,5 +69,6 @@ eventManApp.config(['$routeProvider',
|
||||||
otherwise({
|
otherwise({
|
||||||
redirectTo: '/events'
|
redirectTo: '/events'
|
||||||
});
|
});
|
||||||
}]);
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
16
angular_app/js/controllers.js
vendored
16
angular_app/js/controllers.js
vendored
|
@ -1,9 +1,12 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/* Controllers */
|
/* Controllers; their method are available where specified with the ng-controller
|
||||||
|
* directive or for a given route (see app.js). They use some services to
|
||||||
|
* connect to the backend (see services.js). */
|
||||||
var eventManControllers = angular.module('eventManControllers', []);
|
var eventManControllers = angular.module('eventManControllers', []);
|
||||||
|
|
||||||
|
|
||||||
|
/* A controller that can be used to navigate. */
|
||||||
eventManControllers.controller('navigation', ['$location',
|
eventManControllers.controller('navigation', ['$location',
|
||||||
function ($location) {
|
function ($location) {
|
||||||
this.go = function(url) {
|
this.go = function(url) {
|
||||||
|
@ -26,11 +29,12 @@ eventManControllers.controller('EventDetailsCtrl', ['$scope', 'Event', '$routePa
|
||||||
if ($routeParams.id) {
|
if ($routeParams.id) {
|
||||||
$scope.event = Event.get($routeParams);
|
$scope.event = Event.get($routeParams);
|
||||||
}
|
}
|
||||||
|
// store a new Event or update an existing one
|
||||||
$scope.save = function() {
|
$scope.save = function() {
|
||||||
if ($scope.event.id === undefined) {
|
if ($scope.event.id === undefined) {
|
||||||
Event.save($scope.event);
|
$scope.event = Event.save($scope.event);
|
||||||
} else {
|
} else {
|
||||||
Event.update($scope.event);
|
$scope.event = Event.update($scope.event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}]
|
}]
|
||||||
|
@ -50,11 +54,13 @@ eventManControllers.controller('PersonDetailsCtrl', ['$scope', 'Person', '$route
|
||||||
if ($routeParams.id) {
|
if ($routeParams.id) {
|
||||||
$scope.person = Person.get($routeParams);
|
$scope.person = Person.get($routeParams);
|
||||||
}
|
}
|
||||||
|
// store a new Person or update an existing one
|
||||||
|
$scope.save = function() {
|
||||||
$scope.save = function() {
|
$scope.save = function() {
|
||||||
if ($scope.person.id === undefined) {
|
if ($scope.person.id === undefined) {
|
||||||
Person.save($scope.person);
|
$scope.person = Person.save($scope.person);
|
||||||
} else {
|
} else {
|
||||||
Person.update($scope.person);
|
$scope.person = Person.update($scope.person);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}]
|
}]
|
||||||
|
|
4
angular_app/js/services.js
vendored
4
angular_app/js/services.js
vendored
|
@ -1,5 +1,9 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* Services that are used to interact with the backend. */
|
||||||
var eventManServices = angular.module('eventManServices', ['ngResource']);
|
var eventManServices = angular.module('eventManServices', ['ngResource']);
|
||||||
|
|
||||||
|
|
||||||
eventManServices.factory('Event', ['$resource',
|
eventManServices.factory('Event', ['$resource',
|
||||||
function($resource) {
|
function($resource) {
|
||||||
return $resource('events/:id', {id: '@_id'}, {
|
return $resource('events/:id', {id: '@_id'}, {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
<!-- show details of a single Person (editing also take place here) -->
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<form ng-model="persondetails" ng-submit="save()">
|
<form ng-model="persondetails" ng-submit="save()">
|
||||||
<div class="input-group input-group-lg">
|
<div class="input-group input-group-lg">
|
||||||
|
|
|
@ -1,28 +1,25 @@
|
||||||
<div class="container-fluid">
|
<!-- show a list of Persons -->
|
||||||
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
|
Search: <input ng-model="query">
|
||||||
Search: <input ng-model="query">
|
Sort by:
|
||||||
Sort by:
|
<select ng-model="orderProp">
|
||||||
<select ng-model="orderProp">
|
<option value="name">Alphabetical</option>
|
||||||
<option value="name">Alphabetical</option>
|
<option value="id">ID</option>
|
||||||
<option value="id">ID</option>
|
</select>
|
||||||
</select>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
|
<ul class="persons">
|
||||||
<ul class="persons">
|
<li ng-repeat="person in persons | filter:query | orderBy:orderProp">
|
||||||
<li ng-repeat="person in persons | filter:query | orderBy:orderProp">
|
<a href="/#/persons/{{person._id}}"><span>{{person.name}}</span> <span>{{person.surname}}</span></a>
|
||||||
<a href="/#/persons/{{person._id}}"><span>{{person.name}}</span> <span>{{person.surname}}</span></a>
|
<p>{{person.email}}</p>
|
||||||
<p>{{person.email}}</p>
|
</li>
|
||||||
</li>
|
</ul>
|
||||||
</ul>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
90
backend.py
90
backend.py
|
@ -1,6 +1,19 @@
|
||||||
"""Event Man(ager) backend
|
"""Event Man(ager) database backend
|
||||||
|
|
||||||
Classes and functions used to manage events and attendants.
|
Classes and functions used to manage events and attendees database.
|
||||||
|
|
||||||
|
Copyright 2015 Davide Alberani <da@erlug.linux.it>
|
||||||
|
RaspiBO <info@raspibo.org>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pymongo
|
import pymongo
|
||||||
|
@ -8,15 +21,29 @@ from bson.objectid import ObjectId
|
||||||
|
|
||||||
|
|
||||||
class EventManDB(object):
|
class EventManDB(object):
|
||||||
|
"""MongoDB connector."""
|
||||||
db = None
|
db = None
|
||||||
connection = None
|
connection = None
|
||||||
|
|
||||||
def __init__(self, url=None, dbName='eventman'):
|
def __init__(self, url=None, dbName='eventman'):
|
||||||
|
"""Initialize the instance, connecting to the database.
|
||||||
|
|
||||||
|
:param url: URL of the database
|
||||||
|
:type url: str (or None to connect to localhost)
|
||||||
|
"""
|
||||||
self._url = url
|
self._url = url
|
||||||
self._dbName = dbName
|
self._dbName = dbName
|
||||||
self.connect(url)
|
self.connect(url)
|
||||||
|
|
||||||
def connect(self, url=None, dbName=None):
|
def connect(self, url=None, dbName=None):
|
||||||
|
"""Connect to the database.
|
||||||
|
|
||||||
|
:param url: URL of the database
|
||||||
|
:type url: str (or None to connect to localhost)
|
||||||
|
|
||||||
|
:return: the database we're connected to
|
||||||
|
:rtype: :class:`~pymongo.database.Database`
|
||||||
|
"""
|
||||||
if self.db is not None:
|
if self.db is not None:
|
||||||
return self.db
|
return self.db
|
||||||
if url:
|
if url:
|
||||||
|
@ -28,12 +55,32 @@ class EventManDB(object):
|
||||||
return self.db
|
return self.db
|
||||||
|
|
||||||
def get(self, collection, _id):
|
def get(self, collection, _id):
|
||||||
|
"""Get a single document with the specified `_id`.
|
||||||
|
|
||||||
|
:param collection: search the document in this collection
|
||||||
|
:type collection: str
|
||||||
|
:param _id: unique ID of the document
|
||||||
|
:type _id: str or :class:`~bson.objectid.ObjectId`
|
||||||
|
|
||||||
|
:return: the document with the given `_id`
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
if not isinstance(_id, ObjectId):
|
if not isinstance(_id, ObjectId):
|
||||||
_id = ObjectId(_id)
|
_id = ObjectId(_id)
|
||||||
results = self.query(collection, {'_id': _id})
|
results = self.query(collection, {'_id': _id})
|
||||||
return results and results[0] or {}
|
return results and results[0] or {}
|
||||||
|
|
||||||
def query(self, collection, query=None):
|
def query(self, collection, query=None):
|
||||||
|
"""Get multiple documents matching a query.
|
||||||
|
|
||||||
|
:param collection: search for documents in this collection
|
||||||
|
:type collection: str
|
||||||
|
:param query: search for documents with those attributes
|
||||||
|
:type query: dict or None
|
||||||
|
|
||||||
|
:return: list of matching documents
|
||||||
|
:rtype: list
|
||||||
|
"""
|
||||||
db = self.connect()
|
db = self.connect()
|
||||||
query = query or {}
|
query = query or {}
|
||||||
if'_id' in query and not isinstance(query['_id'], ObjectId):
|
if'_id' in query and not isinstance(query['_id'], ObjectId):
|
||||||
|
@ -44,11 +91,33 @@ class EventManDB(object):
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def add(self, collection, data):
|
def add(self, collection, data):
|
||||||
|
"""Insert a new document.
|
||||||
|
|
||||||
|
:param collection: insert the document in this collection
|
||||||
|
:type collection: str
|
||||||
|
:param data: the document to store
|
||||||
|
:type data: dict
|
||||||
|
|
||||||
|
:return: the document, as created in the database
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
db = self.connect()
|
db = self.connect()
|
||||||
_id = db[collection].insert(data)
|
_id = db[collection].insert(data)
|
||||||
return self.get(collection, _id)
|
return self.get(collection, _id)
|
||||||
|
|
||||||
def update(self, collection, _id, data):
|
def update(self, collection, _id, data):
|
||||||
|
"""Update an existing document.
|
||||||
|
|
||||||
|
:param collection: update a document in this collection
|
||||||
|
:type collection: str
|
||||||
|
:param _id: unique ID of the document to be updatd
|
||||||
|
:type _id: str or :class:`~bson.objectid.ObjectId`
|
||||||
|
:param data: the updated information to store
|
||||||
|
:type data: dict
|
||||||
|
|
||||||
|
:return: the document, after the update
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
db = self.connect()
|
db = self.connect()
|
||||||
data = data or {}
|
data = data or {}
|
||||||
if '_id' in data:
|
if '_id' in data:
|
||||||
|
@ -56,3 +125,20 @@ class EventManDB(object):
|
||||||
db[collection].update({'_id': ObjectId(_id)}, {'$set': data})
|
db[collection].update({'_id': ObjectId(_id)}, {'$set': data})
|
||||||
return self.get(collection, _id)
|
return self.get(collection, _id)
|
||||||
|
|
||||||
|
def delete(self, collection, _id_or_query=None, force=False):
|
||||||
|
"""Remove one or more documents from a collection.
|
||||||
|
|
||||||
|
:param collection: search the documents in this collection
|
||||||
|
:type collection: str
|
||||||
|
:param _id_or_query: unique ID of the document or query to match multiple documents
|
||||||
|
:type _id_or_query: str or :class:`~bson.objectid.ObjectId` or dict
|
||||||
|
:param force: force the deletion of all documents, when `_id_or_query` is empty
|
||||||
|
:type force: bool
|
||||||
|
"""
|
||||||
|
if not _id_or_query and not force:
|
||||||
|
return
|
||||||
|
db = self.connect()
|
||||||
|
if not isinstance(_id_or_query, (ObjectId, dict)):
|
||||||
|
_id_or_query = ObjectId(_id_or_query)
|
||||||
|
db[collection].remove(_id_or_query)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,20 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
"""Event Man(ager)
|
"""Event Man(ager)
|
||||||
|
|
||||||
Your friendly manager of attendants at a conference.
|
Your friendly manager of attendees at an event.
|
||||||
|
|
||||||
|
Copyright 2015 Davide Alberani <da@erlug.linux.it>
|
||||||
|
RaspiBO <info@raspibo.org>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
@ -18,58 +31,84 @@ from tornado import gen, escape
|
||||||
import backend
|
import backend
|
||||||
|
|
||||||
|
|
||||||
class BaseHandler(tornado.web.RequestHandler):
|
|
||||||
def initialize(self, **kwargs):
|
|
||||||
for key, value in kwargs.iteritems():
|
|
||||||
setattr(self, key, value)
|
|
||||||
|
|
||||||
|
|
||||||
class RootHandler(BaseHandler):
|
|
||||||
angular_app_path = os.path.join(os.path.dirname(__file__), "angular_app")
|
|
||||||
@gen.coroutine
|
|
||||||
def get(self):
|
|
||||||
with open(self.angular_app_path + "/index.html", 'r') as fd:
|
|
||||||
self.write(fd.read())
|
|
||||||
|
|
||||||
|
|
||||||
class ImprovedEncoder(json.JSONEncoder):
|
class ImprovedEncoder(json.JSONEncoder):
|
||||||
|
"""Enhance the default JSON encoder to serialize datetime objects."""
|
||||||
def default(self, o):
|
def default(self, o):
|
||||||
if isinstance(o, datetime.datetime):
|
if isinstance(o, (datetime.datetime, datetime.date,
|
||||||
|
datetime.time, datetime.timedelta)):
|
||||||
return str(o)
|
return str(o)
|
||||||
return json.JSONEncoder.default(self, o)
|
return json.JSONEncoder.default(self, o)
|
||||||
|
|
||||||
json._default_encoder = ImprovedEncoder()
|
json._default_encoder = ImprovedEncoder()
|
||||||
|
|
||||||
|
|
||||||
|
class BaseHandler(tornado.web.RequestHandler):
|
||||||
|
"""Base class for request handlers."""
|
||||||
|
def initialize(self, **kwargs):
|
||||||
|
"""Add every passed (key, value) as attributes of the instance."""
|
||||||
|
for key, value in kwargs.iteritems():
|
||||||
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
|
||||||
|
class RootHandler(BaseHandler):
|
||||||
|
"""Handler for the / path."""
|
||||||
|
angular_app_path = os.path.join(os.path.dirname(__file__), "angular_app")
|
||||||
|
|
||||||
|
@gen.coroutine
|
||||||
|
def get(self):
|
||||||
|
# serve the ./angular_app/index.html file
|
||||||
|
with open(self.angular_app_path + "/index.html", 'r') as fd:
|
||||||
|
self.write(fd.read())
|
||||||
|
|
||||||
|
|
||||||
class CollectionHandler(BaseHandler):
|
class CollectionHandler(BaseHandler):
|
||||||
|
"""Base class for handlers that need to interact with the database backend.
|
||||||
|
|
||||||
|
Introduce basic CRUD operations."""
|
||||||
|
# set of documents we're managing (a collection in MongoDB or a table in a SQL database)
|
||||||
collection = None
|
collection = None
|
||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def get(self, id_=None):
|
def get(self, id_=None):
|
||||||
if id_ is not None:
|
if id_ is not None:
|
||||||
|
# read a single document
|
||||||
self.write(self.db.get(self.collection, id_))
|
self.write(self.db.get(self.collection, id_))
|
||||||
else:
|
else:
|
||||||
|
# return an object containing the list of all objects in the collection;
|
||||||
|
# e.g.: {'events': [{'_id': 'obj1-id, ...}, {'_id': 'obj2-id, ...}, ...]}
|
||||||
|
# Please, never return JSON lists that are not encapsulated in an object,
|
||||||
|
# to avoid XSS vulnerabilities.
|
||||||
self.write({self.collection: self.db.query(self.collection)})
|
self.write({self.collection: self.db.query(self.collection)})
|
||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def post(self, id_=None, **kwargs):
|
def post(self, id_=None, **kwargs):
|
||||||
data = escape.json_decode(self.request.body or {})
|
data = escape.json_decode(self.request.body or {})
|
||||||
if id_ is None:
|
if id_ is None:
|
||||||
|
# insert a new document
|
||||||
newData = self.db.add(self.collection, data)
|
newData = self.db.add(self.collection, data)
|
||||||
else:
|
else:
|
||||||
|
# update an existing document
|
||||||
newData = self.db.update(self.collection, id_, data)
|
newData = self.db.update(self.collection, id_, data)
|
||||||
self.write(newData)
|
self.write(newData)
|
||||||
|
|
||||||
|
# PUT is handled by the POST method
|
||||||
put = post
|
put = post
|
||||||
|
|
||||||
|
|
||||||
class PersonsHandler(CollectionHandler):
|
class PersonsHandler(CollectionHandler):
|
||||||
|
"""Handle requests for Persons."""
|
||||||
collection = 'persons'
|
collection = 'persons'
|
||||||
|
|
||||||
|
|
||||||
class EventsHandler(CollectionHandler):
|
class EventsHandler(CollectionHandler):
|
||||||
|
"""Handle requests for Events."""
|
||||||
collection = 'events'
|
collection = 'events'
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def run():
|
||||||
|
"""Run the Tornado web application."""
|
||||||
|
# command line arguments; can also be written in a configuration file,
|
||||||
|
# specified with the --config argument.
|
||||||
define("port", default=5242, help="run on the given port", type=int)
|
define("port", default=5242, help="run on the given port", type=int)
|
||||||
define("data", default=os.path.join(os.path.dirname(__file__), "data"),
|
define("data", default=os.path.join(os.path.dirname(__file__), "data"),
|
||||||
help="specify the directory used to store the data")
|
help="specify the directory used to store the data")
|
||||||
|
@ -82,6 +121,7 @@ def main():
|
||||||
callback=lambda path: tornado.options.parse_config_file(path, final=False))
|
callback=lambda path: tornado.options.parse_config_file(path, final=False))
|
||||||
tornado.options.parse_command_line()
|
tornado.options.parse_command_line()
|
||||||
|
|
||||||
|
# database backend connector
|
||||||
db_connector = backend.EventManDB(url=options.mongodbURL, dbName=options.dbName)
|
db_connector = backend.EventManDB(url=options.mongodbURL, dbName=options.dbName)
|
||||||
init_params = dict(db=db_connector)
|
init_params = dict(db=db_connector)
|
||||||
|
|
||||||
|
@ -100,5 +140,5 @@ def main():
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
run()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue