fixes #29: port to Python 3 - welcome to the future
This commit is contained in:
parent
26296974cb
commit
83b29fd2d4
6 changed files with 56 additions and 32 deletions
|
@ -22,10 +22,9 @@ See [https://ibt2.ismito.it:3002/](https://ibt2.ismito.it:3002/)
|
||||||
To install it:
|
To install it:
|
||||||
``` bash
|
``` bash
|
||||||
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
|
sudo pip3 install tornado
|
||||||
sudo pip install pymongo
|
sudo pip3 install pymongo
|
||||||
sudo pip install python-dateutil
|
|
||||||
git clone https://github.com/raspibo/ibt2
|
git clone https://github.com/raspibo/ibt2
|
||||||
cd ibt2
|
cd ibt2
|
||||||
```
|
```
|
||||||
|
@ -62,7 +61,7 @@ npm run devserver &
|
||||||
npm run dev
|
npm run dev
|
||||||
|
|
||||||
# only when the devserver is running, you can also run the testsuite
|
# only when the devserver is running, you can also run the testsuite
|
||||||
python ./tests/ibt2_tests.py
|
python3 ./tests/ibt2_tests.py
|
||||||
```
|
```
|
||||||
|
|
||||||
Your browser will automatically open [http://localhost:8080/](http://localhost:8080/) (that's the server for development)
|
Your browser will automatically open [http://localhost:8080/](http://localhost:8080/) (that's the server for development)
|
||||||
|
|
20
ibt2.py
20
ibt2.py
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
"""I'll Be There, 2 (ibt2) - an oversimplified attendees registration system.
|
"""I'll Be There, 2 (ibt2) - an oversimplified attendees registration system.
|
||||||
|
|
||||||
Copyright 2016-2017 Davide Alberani <da@erlug.linux.it>
|
Copyright 2016-2017 Davide Alberani <da@erlug.linux.it>
|
||||||
|
@ -63,8 +63,8 @@ class BaseHandler(tornado.web.RequestHandler):
|
||||||
collection = None
|
collection = None
|
||||||
|
|
||||||
# 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()]))
|
||||||
|
|
||||||
# Arguments suitable for a query on MongoDB.
|
# Arguments suitable for a query on MongoDB.
|
||||||
clean_arguments = property(lambda self: self._clean_dict(self.arguments))
|
clean_arguments = property(lambda self: self._clean_dict(self.arguments))
|
||||||
|
@ -86,8 +86,8 @@ class BaseHandler(tornado.web.RequestHandler):
|
||||||
: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('$')) or key in ('_id', 'created_by',
|
if (isinstance(key, str) and key.startswith('$')) or key in ('_id', 'created_by',
|
||||||
'created_at', 'updated_by',
|
'created_at', 'updated_by',
|
||||||
'updated_at'):
|
'updated_at'):
|
||||||
del data[key]
|
del data[key]
|
||||||
|
@ -109,13 +109,16 @@ class BaseHandler(tornado.web.RequestHandler):
|
||||||
|
|
||||||
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 ID from the secure cookie."""
|
"""Retrieve current user ID 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):
|
||||||
|
@ -381,8 +384,7 @@ class GroupsHandler(BaseHandler):
|
||||||
if not (day and group):
|
if not (day and group):
|
||||||
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.current_user_info.get('isAdmin'):
|
if not self.current_user_info.get('isAdmin'):
|
||||||
self.build_error(status=401, message='insufficient permissions: must be admin')
|
return self.build_error(status=401, message='insufficient permissions: must be admin')
|
||||||
return False
|
|
||||||
query = {'day': day, 'group': group}
|
query = {'day': day, 'group': group}
|
||||||
howMany = self.db.delete('attendees', query)
|
howMany = self.db.delete('attendees', query)
|
||||||
self.db.delete('groups', query)
|
self.db.delete('groups', query)
|
||||||
|
|
4
monco.py
4
monco.py
|
@ -56,7 +56,7 @@ 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:
|
||||||
try:
|
try:
|
||||||
d[key] = _force_conversion[key](item)
|
d[key] = _force_conversion[key](item)
|
||||||
|
@ -254,7 +254,7 @@ class Monco(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,
|
||||||
|
|
|
@ -15,8 +15,8 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node build/dev-server.js",
|
"dev": "node build/dev-server.js",
|
||||||
"devserver": "python ibt2.py --debug --db_name=ibt2_test",
|
"devserver": "python3 ibt2.py --debug --db_name=ibt2_test",
|
||||||
"server": "python ibt2.py --debug",
|
"server": "python3 ibt2.py --debug",
|
||||||
"build": "node build/build.js"
|
"build": "node build/build.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
"""I'll Be There, 2 (ibt2) - tests
|
"""I'll Be There, 2 (ibt2) - tests
|
||||||
|
|
||||||
Copyright 2016-2017 Davide Alberani <da@erlug.linux.it>
|
Copyright 2016-2017 Davide Alberani <da@erlug.linux.it>
|
||||||
|
@ -23,7 +23,7 @@ BASE_URL = 'http://localhost:3000/v1.1/'
|
||||||
DB_NAME = 'ibt2_test'
|
DB_NAME = 'ibt2_test'
|
||||||
|
|
||||||
def dictInDict(d, dContainer):
|
def dictInDict(d, dContainer):
|
||||||
for k, v in d.viewitems():
|
for k, v in d.items():
|
||||||
if k not in dContainer:
|
if k not in dContainer:
|
||||||
return False
|
return False
|
||||||
if v != dContainer[k]:
|
if v != dContainer[k]:
|
||||||
|
@ -39,15 +39,15 @@ class Ibt2Tests(unittest.TestCase):
|
||||||
self.db['attendees'].drop()
|
self.db['attendees'].drop()
|
||||||
self.db['days'].drop()
|
self.db['days'].drop()
|
||||||
self.db['groups'].drop()
|
self.db['groups'].drop()
|
||||||
self.db['users'].remove({'username': 'newuser'})
|
self.db['users'].delete_one({'username': 'newuser'})
|
||||||
self.db['users'].remove({'username': 'newuser2'})
|
self.db['users'].delete_one({'username': 'newuser2'})
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.db['attendees'].drop()
|
self.db['attendees'].drop()
|
||||||
self.db['days'].drop()
|
self.db['days'].drop()
|
||||||
self.db['groups'].drop()
|
self.db['groups'].drop()
|
||||||
self.db['users'].remove({'username': 'newuser'})
|
self.db['users'].delete_one({'username': 'newuser'})
|
||||||
self.db['users'].remove({'username': 'newuser2'})
|
self.db['users'].delete_one({'username': 'newuser2'})
|
||||||
|
|
||||||
def add_attendee(self, attendee):
|
def add_attendee(self, attendee):
|
||||||
r = requests.post('%sattendees' % BASE_URL, json=attendee)
|
r = requests.post('%sattendees' % BASE_URL, json=attendee)
|
||||||
|
@ -85,9 +85,11 @@ class Ibt2Tests(unittest.TestCase):
|
||||||
attendee = {'name': 'A Name', 'day': '2017-01-15', 'group': 'A group'}
|
attendee = {'name': 'A Name', 'day': '2017-01-15', 'group': 'A group'}
|
||||||
r = self.add_attendee(attendee)
|
r = self.add_attendee(attendee)
|
||||||
id_ = r.json().get('_id')
|
id_ = r.json().get('_id')
|
||||||
|
r.connection.close()
|
||||||
r = requests.delete(BASE_URL + 'attendees/' + id_)
|
r = requests.delete(BASE_URL + 'attendees/' + id_)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
self.assertTrue(r.json().get('success'))
|
self.assertTrue(r.json().get('success'))
|
||||||
|
r.connection.close()
|
||||||
|
|
||||||
def test_get_days(self):
|
def test_get_days(self):
|
||||||
self.add_attendee({'day': '2017-01-15', 'name': 'A name', 'group': 'group A'})
|
self.add_attendee({'day': '2017-01-15', 'name': 'A name', 'group': 'group A'})
|
||||||
|
@ -118,9 +120,11 @@ class Ibt2Tests(unittest.TestCase):
|
||||||
def test_create_user(self):
|
def test_create_user(self):
|
||||||
r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt2'})
|
r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt2'})
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
r.connection.close()
|
||||||
s = self.login('newuser', 'ibt2')
|
s = self.login('newuser', 'ibt2')
|
||||||
r = s.get(BASE_URL + 'users/current')
|
r = s.get(BASE_URL + 'users/current')
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
r.connection.close()
|
||||||
|
|
||||||
def test_update_user(self):
|
def test_update_user(self):
|
||||||
r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt2'})
|
r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt2'})
|
||||||
|
@ -130,17 +134,20 @@ class Ibt2Tests(unittest.TestCase):
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
id2_ = r.json()['_id']
|
id2_ = r.json()['_id']
|
||||||
r = requests.put(BASE_URL + 'users/' + id_, json={'email': 't@example.com'})
|
r = requests.put(BASE_URL + 'users/' + id_, json={'email': 't@example.com'})
|
||||||
self.assertRaises(r.raise_for_status)
|
self.assertRaises(requests.exceptions.HTTPError, r.raise_for_status)
|
||||||
s = self.login('newuser', 'ibt2')
|
s = self.login('newuser', 'ibt2')
|
||||||
r = s.put(BASE_URL + 'users/' + id_, json={'email': 'test@example.com'})
|
r = s.put(BASE_URL + 'users/' + id_, json={'email': 'test@example.com'})
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
self.assertEqual(r.json().get('email'), 'test@example.com')
|
self.assertEqual(r.json().get('email'), 'test@example.com')
|
||||||
|
r.connection.close()
|
||||||
r = s.put(BASE_URL + 'users/' + id2_, json={'email': 'test@example.com'})
|
r = s.put(BASE_URL + 'users/' + id2_, json={'email': 'test@example.com'})
|
||||||
self.assertRaises(r.raise_for_status)
|
self.assertRaises(requests.exceptions.HTTPError, r.raise_for_status)
|
||||||
|
r.connection.close()
|
||||||
s = self.login('admin', 'ibt2')
|
s = self.login('admin', 'ibt2')
|
||||||
r = s.put(BASE_URL + 'users/' + id_, json={'email': 'test2@example.com'})
|
r = s.put(BASE_URL + 'users/' + id_, json={'email': 'test2@example.com'})
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
self.assertEqual(r.json().get('email'), 'test2@example.com')
|
self.assertEqual(r.json().get('email'), 'test2@example.com')
|
||||||
|
r.connection.close()
|
||||||
|
|
||||||
def test_delete_user(self):
|
def test_delete_user(self):
|
||||||
r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt2'})
|
r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt2'})
|
||||||
|
@ -150,26 +157,31 @@ class Ibt2Tests(unittest.TestCase):
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
id2_ = r.json()['_id']
|
id2_ = r.json()['_id']
|
||||||
r = requests.delete(BASE_URL + 'users/' + id_)
|
r = requests.delete(BASE_URL + 'users/' + id_)
|
||||||
self.assertRaises(r.raise_for_status)
|
self.assertRaises(requests.exceptions.HTTPError, r.raise_for_status)
|
||||||
|
r.connection.close()
|
||||||
s = self.login('newuser', 'ibt2')
|
s = self.login('newuser', 'ibt2')
|
||||||
r = s.delete(BASE_URL + 'users/' + id_)
|
r = s.delete(BASE_URL + 'users/' + id_)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
r.connection.close()
|
||||||
r = s.delete(BASE_URL + 'users/' + id2_)
|
r = s.delete(BASE_URL + 'users/' + id2_)
|
||||||
self.assertRaises(r.raise_for_status)
|
self.assertRaises(requests.exceptions.HTTPError, r.raise_for_status)
|
||||||
|
r.connection.close()
|
||||||
s = self.login('admin', 'ibt2')
|
s = self.login('admin', 'ibt2')
|
||||||
r = s.delete(BASE_URL + 'users/' + id2_)
|
r = s.delete(BASE_URL + 'users/' + id2_)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
r.connection.close()
|
||||||
|
|
||||||
def test_duplicate_user(self):
|
def test_duplicate_user(self):
|
||||||
r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt2'})
|
r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt2'})
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt3'})
|
r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt3'})
|
||||||
self.assertRaises(r.raise_for_status)
|
self.assertRaises(requests.exceptions.HTTPError, r.raise_for_status)
|
||||||
|
|
||||||
def login(self, username, password):
|
def login(self, username, password):
|
||||||
s = requests.Session()
|
s = requests.Session()
|
||||||
r = s.post(BASE_URL + 'login', json={'username': username, 'password': password})
|
r = s.post(BASE_URL + 'login', json={'username': username, 'password': password})
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
r.connection.close()
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def test_created_by(self):
|
def test_created_by(self):
|
||||||
|
@ -177,12 +189,14 @@ class Ibt2Tests(unittest.TestCase):
|
||||||
r = s.get(BASE_URL + 'users/current')
|
r = s.get(BASE_URL + 'users/current')
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
user_id = r.json()['_id']
|
user_id = r.json()['_id']
|
||||||
|
r.connection.close()
|
||||||
attendee = {'day': '2017-01-15', 'name': 'A name', 'group': 'group A'}
|
attendee = {'day': '2017-01-15', 'name': 'A name', 'group': 'group A'}
|
||||||
r = s.post('%sattendees' % BASE_URL, json=attendee)
|
r = s.post('%sattendees' % BASE_URL, json=attendee)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
rj = r.json()
|
rj = r.json()
|
||||||
self.assertEqual(user_id, rj['created_by'])
|
self.assertEqual(user_id, rj['created_by'])
|
||||||
self.assertEqual(user_id, rj['updated_by'])
|
self.assertEqual(user_id, rj['updated_by'])
|
||||||
|
r.connection.close()
|
||||||
|
|
||||||
def test_put_day(self):
|
def test_put_day(self):
|
||||||
day = {'day': '2017-01-16', 'notes': 'A day note'}
|
day = {'day': '2017-01-16', 'notes': 'A day note'}
|
||||||
|
@ -214,10 +228,12 @@ class Ibt2Tests(unittest.TestCase):
|
||||||
r = s.delete(BASE_URL + 'days/2017-01-16/groups/A group', params={'day': '2017-01-16', 'group': 'A group'})
|
r = s.delete(BASE_URL + 'days/2017-01-16/groups/A group', params={'day': '2017-01-16', 'group': 'A group'})
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
rj = r.json()
|
rj = r.json()
|
||||||
|
r.connection.close()
|
||||||
r = requests.get(BASE_URL + 'days/2017-01-16')
|
r = requests.get(BASE_URL + 'days/2017-01-16')
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
rj = r.json()
|
rj = r.json()
|
||||||
self.assertTrue(rj == {})
|
self.assertTrue(rj == {})
|
||||||
|
r.connection.close()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main(verbosity=2)
|
unittest.main(verbosity=2)
|
||||||
|
|
13
utils.py
13
utils.py
|
@ -36,15 +36,22 @@ 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))
|
pass_and_salt = '%s%s' % (salt, password)
|
||||||
|
pass_and_salt = pass_and_salt.encode('utf-8', 'ignore')
|
||||||
|
hash_ = hashlib.sha512(pass_and_salt)
|
||||||
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)
|
||||||
|
|
Loading…
Reference in a new issue