fixes #29: port to Python 3 - welcome to the future

This commit is contained in:
Davide Alberani 2017-02-12 14:16:25 +01:00
parent 26296974cb
commit 83b29fd2d4
6 changed files with 56 additions and 32 deletions

View file

@ -22,10 +22,9 @@ See [https://ibt2.ismito.it:3002/](https://ibt2.ismito.it:3002/)
To install it:
``` bash
wget https://bootstrap.pypa.io/get-pip.py
sudo python get-pip.py
sudo pip install tornado
sudo pip install pymongo
sudo pip install python-dateutil
sudo python3 get-pip.py
sudo pip3 install tornado
sudo pip3 install pymongo
git clone https://github.com/raspibo/ibt2
cd ibt2
```
@ -62,7 +61,7 @@ npm run devserver &
npm run dev
# 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)

20
ibt2.py
View file

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
"""I'll Be There, 2 (ibt2) - an oversimplified attendees registration system.
Copyright 2016-2017 Davide Alberani <da@erlug.linux.it>
@ -63,8 +63,8 @@ class BaseHandler(tornado.web.RequestHandler):
collection = None
# 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()]))
# Arguments suitable for a query on MongoDB.
clean_arguments = property(lambda self: self._clean_dict(self.arguments))
@ -86,8 +86,8 @@ class BaseHandler(tornado.web.RequestHandler):
: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('$')) or key in ('_id', 'created_by',
for key in list(data.keys()):
if (isinstance(key, str) and key.startswith('$')) or key in ('_id', 'created_by',
'created_at', 'updated_by',
'updated_at'):
del data[key]
@ -109,13 +109,16 @@ class BaseHandler(tornado.web.RequestHandler):
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 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
def current_user_info(self):
@ -381,8 +384,7 @@ class GroupsHandler(BaseHandler):
if not (day and group):
return self.build_error(status=404, message='unable to access the resource')
if not self.current_user_info.get('isAdmin'):
self.build_error(status=401, message='insufficient permissions: must be admin')
return False
return self.build_error(status=401, message='insufficient permissions: must be admin')
query = {'day': day, 'group': group}
howMany = self.db.delete('attendees', query)
self.db.delete('groups', query)

View file

@ -56,7 +56,7 @@ 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:
try:
d[key] = _force_conversion[key](item)
@ -254,7 +254,7 @@ class Monco(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,

View file

@ -15,8 +15,8 @@
},
"scripts": {
"dev": "node build/dev-server.js",
"devserver": "python ibt2.py --debug --db_name=ibt2_test",
"server": "python ibt2.py --debug",
"devserver": "python3 ibt2.py --debug --db_name=ibt2_test",
"server": "python3 ibt2.py --debug",
"build": "node build/build.js"
},
"dependencies": {

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
"""I'll Be There, 2 (ibt2) - tests
Copyright 2016-2017 Davide Alberani <da@erlug.linux.it>
@ -23,7 +23,7 @@ BASE_URL = 'http://localhost:3000/v1.1/'
DB_NAME = 'ibt2_test'
def dictInDict(d, dContainer):
for k, v in d.viewitems():
for k, v in d.items():
if k not in dContainer:
return False
if v != dContainer[k]:
@ -39,15 +39,15 @@ class Ibt2Tests(unittest.TestCase):
self.db['attendees'].drop()
self.db['days'].drop()
self.db['groups'].drop()
self.db['users'].remove({'username': 'newuser'})
self.db['users'].remove({'username': 'newuser2'})
self.db['users'].delete_one({'username': 'newuser'})
self.db['users'].delete_one({'username': 'newuser2'})
def tearDown(self):
self.db['attendees'].drop()
self.db['days'].drop()
self.db['groups'].drop()
self.db['users'].remove({'username': 'newuser'})
self.db['users'].remove({'username': 'newuser2'})
self.db['users'].delete_one({'username': 'newuser'})
self.db['users'].delete_one({'username': 'newuser2'})
def add_attendee(self, 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'}
r = self.add_attendee(attendee)
id_ = r.json().get('_id')
r.connection.close()
r = requests.delete(BASE_URL + 'attendees/' + id_)
r.raise_for_status()
self.assertTrue(r.json().get('success'))
r.connection.close()
def test_get_days(self):
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):
r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt2'})
r.raise_for_status()
r.connection.close()
s = self.login('newuser', 'ibt2')
r = s.get(BASE_URL + 'users/current')
r.raise_for_status()
r.connection.close()
def test_update_user(self):
r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt2'})
@ -130,17 +134,20 @@ class Ibt2Tests(unittest.TestCase):
r.raise_for_status()
id2_ = r.json()['_id']
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')
r = s.put(BASE_URL + 'users/' + id_, json={'email': 'test@example.com'})
r.raise_for_status()
self.assertEqual(r.json().get('email'), 'test@example.com')
r.connection.close()
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')
r = s.put(BASE_URL + 'users/' + id_, json={'email': 'test2@example.com'})
r.raise_for_status()
self.assertEqual(r.json().get('email'), 'test2@example.com')
r.connection.close()
def test_delete_user(self):
r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt2'})
@ -150,26 +157,31 @@ class Ibt2Tests(unittest.TestCase):
r.raise_for_status()
id2_ = r.json()['_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')
r = s.delete(BASE_URL + 'users/' + id_)
r.raise_for_status()
r.connection.close()
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')
r = s.delete(BASE_URL + 'users/' + id2_)
r.raise_for_status()
r.connection.close()
def test_duplicate_user(self):
r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt2'})
r.raise_for_status()
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):
s = requests.Session()
r = s.post(BASE_URL + 'login', json={'username': username, 'password': password})
r.raise_for_status()
r.connection.close()
return s
def test_created_by(self):
@ -177,12 +189,14 @@ class Ibt2Tests(unittest.TestCase):
r = s.get(BASE_URL + 'users/current')
r.raise_for_status()
user_id = r.json()['_id']
r.connection.close()
attendee = {'day': '2017-01-15', 'name': 'A name', 'group': 'group A'}
r = s.post('%sattendees' % BASE_URL, json=attendee)
r.raise_for_status()
rj = r.json()
self.assertEqual(user_id, rj['created_by'])
self.assertEqual(user_id, rj['updated_by'])
r.connection.close()
def test_put_day(self):
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.raise_for_status()
rj = r.json()
r.connection.close()
r = requests.get(BASE_URL + 'days/2017-01-16')
r.raise_for_status()
rj = r.json()
self.assertTrue(rj == {})
r.connection.close()
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -36,15 +36,22 @@ 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))
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())
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)