From 1395c2996f3739a37776fe83363e238821097f3a Mon Sep 17 00:00:00 2001 From: benoitguigal Date: Wed, 27 May 2015 08:59:38 +0200 Subject: [PATCH 1/9] make modification to persistent Installation at the very end of update to prevent conflict --- figureraspbian/db.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/figureraspbian/db.py b/figureraspbian/db.py index 5d74c21..b898c5d 100644 --- a/figureraspbian/db.py +++ b/figureraspbian/db.py @@ -148,23 +148,25 @@ def update(self): """ Update the installation from Figure API """ installation = api.get_installation() if installation is not None: - is_new = self.id != installation['id'] - self.start = installation['start'] - self.end = installation['end'] - self.id = installation['id'] - self.scenario = installation['scenario'] - self.ticket_template = self.scenario['ticket_template'] for image in self.ticket_template['images']: api.download(image['image'], IMAGE_DIR) for image_variable in self.ticket_template['image_variables']: for image in image_variable['items']: api.download(image['image'], IMAGE_DIR) - if is_new: - self.codes = api.get_codes(self.id) - self._p_changed = True + is_new = self.id != installation['id'] + new_codes = api.get_codes(self.id) if is_new else None ticket_css_url = "%s/%s" % (settings.API_HOST, 'static/css/ticket.css') api.download(ticket_css_url, settings.STATIC_ROOT) + if new_codes: + self.codes = new_codes + self.start = installation['start'] + self.end = installation['end'] + self.id = installation['id'] + self.scenario = installation['scenario'] + self.ticket_template = self.scenario['ticket_template'] + self._p_changed = True + def get_code(self): # claim a code code = self.codes.pop() From 05bcdb04435baa975d2df7e858c43e951f6d4569 Mon Sep 17 00:00:00 2001 From: benoitguigal Date: Wed, 27 May 2015 09:04:59 +0200 Subject: [PATCH 2/9] use io.open instead of codecs.open --- figureraspbian/processus.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/figureraspbian/processus.py b/figureraspbian/processus.py index caaac49..865ae85 100644 --- a/figureraspbian/processus.py +++ b/figureraspbian/processus.py @@ -6,7 +6,7 @@ import logging logging.basicConfig(level='INFO') logger = logging.getLogger(__name__) -import codecs +import io from PIL import Image from . import devices, settings, ticketrenderer @@ -41,6 +41,10 @@ def run(): # Render ticket start = time.time() code = db.get_code() + end = time.time() + logger.info('Successfully claimed code in %s seconds', end - start) + + start = time.time() random_text_selections = [ticketrenderer.random_selection(variable) for variable in ticket_template['text_variables']] @@ -57,7 +61,7 @@ def run(): random_image_selections) ticket_html_path = join(settings.STATIC_ROOT, 'ticket.html') - with codecs.open(ticket_html_path, 'w', 'utf-8') as ticket_html: + with io.open(ticket_html_path, mode='w', encoding='utf-8') as ticket_html: ticket_html.write(rendered_html) # get ticket as base64 stream From 67786035cfe5891abbcef94e728bce3b1e7418dd Mon Sep 17 00:00:00 2001 From: benoitguigal Date: Wed, 27 May 2015 09:35:06 +0200 Subject: [PATCH 3/9] download images only if necessary --- figureraspbian/__main__.py | 9 ++++++++- figureraspbian/db.py | 19 +++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/figureraspbian/__main__.py b/figureraspbian/__main__.py index f8ea69d..8a9b362 100644 --- a/figureraspbian/__main__.py +++ b/figureraspbian/__main__.py @@ -7,7 +7,7 @@ import pifacedigitalio -from . import processus, settings, utils +from . import processus, settings, api from .db import Database, managed # Log configuration @@ -40,6 +40,13 @@ def get_listener(): with managed(Database()) as db: db.update_installation() + logger.info("Downloading ticket css...") + ticket_css_url = "%s/%s" % (settings.API_HOST, 'static/css/ticket.css') + try: + api.download(ticket_css_url, settings.STATIC_ROOT) + except Exception: + pass + listener = get_listener() try: diff --git a/figureraspbian/db.py b/figureraspbian/db.py index b898c5d..31a291a 100644 --- a/figureraspbian/db.py +++ b/figureraspbian/db.py @@ -147,24 +147,27 @@ def __init__(self): def update(self): """ Update the installation from Figure API """ installation = api.get_installation() + if installation is not None: - for image in self.ticket_template['images']: - api.download(image['image'], IMAGE_DIR) - for image_variable in self.ticket_template['image_variables']: - for image in image_variable['items']: + scenario = installation['scenario'] + ticket_template = scenario['ticket_template'] + if ticket_template['images'] != self.ticket_template['images']: + for image in ticket_template['images']: api.download(image['image'], IMAGE_DIR) + if ticket_template['image_variables'] != self.ticket_template['image_variables']: + for image_variable in self.ticket_template['image_variables']: + for image in image_variable['items']: + api.download(image['image'], IMAGE_DIR) is_new = self.id != installation['id'] new_codes = api.get_codes(self.id) if is_new else None - ticket_css_url = "%s/%s" % (settings.API_HOST, 'static/css/ticket.css') - api.download(ticket_css_url, settings.STATIC_ROOT) if new_codes: self.codes = new_codes self.start = installation['start'] self.end = installation['end'] self.id = installation['id'] - self.scenario = installation['scenario'] - self.ticket_template = self.scenario['ticket_template'] + self.scenario = scenario + self.ticket_template = ticket_template self._p_changed = True def get_code(self): From bccf303a0a6c732c5f4938c84a21dca46ca1b770 Mon Sep 17 00:00:00 2001 From: benoitguigal Date: Wed, 27 May 2015 09:45:48 +0200 Subject: [PATCH 4/9] device belong to only one user. Use As-User header and staff token --- figureraspbian/api.py | 7 ++++--- figureraspbian/settings.py | 5 ++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/figureraspbian/api.py b/figureraspbian/api.py index f5f50ba..13b6ee1 100644 --- a/figureraspbian/api.py +++ b/figureraspbian/api.py @@ -17,17 +17,18 @@ class ApiException(Exception): session = requests.Session() session.headers.update({ + 'As-User': settings.USER, 'Authorization': 'Bearer %s' % settings.TOKEN, - 'Accept': 'application/json' + 'Accept': 'application/json', }) def get_installation(): - url = "%s/resiniodevices/%s/" % (settings.API_HOST, settings.RESIN_DEVICE_UUID) + url = "%s/installations/active/" % settings.API_HOST r = session.get(url=url, timeout=10) if r.status_code == 200: r.encoding = 'utf-8' - return json.loads(r.text)['active_installation'] + return json.loads(r.text) else: raise ApiException("Failed retrieving installation") diff --git a/figureraspbian/settings.py b/figureraspbian/settings.py index cbf9168..df84b1d 100644 --- a/figureraspbian/settings.py +++ b/figureraspbian/settings.py @@ -32,9 +32,12 @@ def get_env_setting(setting, default=None): # Http host of the API API_HOST = get_env_setting('API_HOST', 'http://localhost:8000') -# Access Token to authenticate user to the API +# Token to authenticate to the API TOKEN = get_env_setting('TOKEN') +# User to whom the device is belonging +USER = get_env_setting('USER') + # Root directory for static files STATIC_ROOT = get_env_setting('STATIC_ROOT', '/Users/benoit/git/figure-raspbian/static') From 6decd64cdfb567e11780dd7764ec4e89bddb5e27 Mon Sep 17 00:00:00 2001 From: benoitguigal Date: Thu, 28 May 2015 17:45:12 +0200 Subject: [PATCH 5/9] handle 404 in db.update --- figureraspbian/api.py | 2 ++ figureraspbian/db.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/figureraspbian/api.py b/figureraspbian/api.py index 13b6ee1..7c35dd6 100644 --- a/figureraspbian/api.py +++ b/figureraspbian/api.py @@ -29,6 +29,8 @@ def get_installation(): if r.status_code == 200: r.encoding = 'utf-8' return json.loads(r.text) + elif r.status_code == 404: + return None else: raise ApiException("Failed retrieving installation") diff --git a/figureraspbian/db.py b/figureraspbian/db.py index 31a291a..85bb544 100644 --- a/figureraspbian/db.py +++ b/figureraspbian/db.py @@ -169,6 +169,9 @@ def update(self): self.scenario = scenario self.ticket_template = ticket_template self._p_changed = True + else: + self.__init__() + self._p_changed = True def get_code(self): # claim a code From bf2e5e10d0e52cdda20dfda35acb48fdb4c54f92 Mon Sep 17 00:00:00 2001 From: benoitguigal Date: Mon, 1 Jun 2015 17:07:26 +0200 Subject: [PATCH 6/9] change USER by FIGURE_USER --- figureraspbian/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/figureraspbian/settings.py b/figureraspbian/settings.py index df84b1d..edc8652 100644 --- a/figureraspbian/settings.py +++ b/figureraspbian/settings.py @@ -36,7 +36,7 @@ def get_env_setting(setting, default=None): TOKEN = get_env_setting('TOKEN') # User to whom the device is belonging -USER = get_env_setting('USER') +USER = get_env_setting('FIGURE_USER') # Root directory for static files STATIC_ROOT = get_env_setting('STATIC_ROOT', '/Users/benoit/git/figure-raspbian/static') From 59e3deab36404cb0cc696703a0b9f4964d8a7124 Mon Sep 17 00:00:00 2001 From: benoitguigal Date: Mon, 1 Jun 2015 17:12:51 +0200 Subject: [PATCH 7/9] as-user should be an int --- figureraspbian/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/figureraspbian/api.py b/figureraspbian/api.py index 7c35dd6..03a9fd0 100644 --- a/figureraspbian/api.py +++ b/figureraspbian/api.py @@ -17,7 +17,7 @@ class ApiException(Exception): session = requests.Session() session.headers.update({ - 'As-User': settings.USER, + 'As-User': int(settings.USER), 'Authorization': 'Bearer %s' % settings.TOKEN, 'Accept': 'application/json', }) From 81289db1ebf7d9b720e350a742f7afeb11578436 Mon Sep 17 00:00:00 2001 From: benoitguigal Date: Tue, 2 Jun 2015 10:22:18 +0200 Subject: [PATCH 8/9] fix download images --- figureraspbian/db.py | 26 +++++----- figureraspbian/settings.py | 3 -- figureraspbian/tests.py | 99 ++++++++++++++++++-------------------- 3 files changed, 63 insertions(+), 65 deletions(-) diff --git a/figureraspbian/db.py b/figureraspbian/db.py index 85bb544..08deb53 100644 --- a/figureraspbian/db.py +++ b/figureraspbian/db.py @@ -147,17 +147,24 @@ def __init__(self): def update(self): """ Update the installation from Figure API """ installation = api.get_installation() - if installation is not None: scenario = installation['scenario'] ticket_template = scenario['ticket_template'] - if ticket_template['images'] != self.ticket_template['images']: - for image in ticket_template['images']: - api.download(image['image'], IMAGE_DIR) - if ticket_template['image_variables'] != self.ticket_template['image_variables']: - for image_variable in self.ticket_template['image_variables']: - for image in image_variable['items']: - api.download(image['image'], IMAGE_DIR) + # Download all images that have not been previously downloaded + items = [image_variable['items'] for image_variable in ticket_template['image_variables']] + items.append(ticket_template['images']) + items = [item for sub_items in items for item in sub_items] + items = map(lambda x: x['image'], items) + if not self.ticket_template: + local_items = [] + else: + local_items = [image_variable['items'] for image_variable in self.ticket_template['image_variables']] + local_items.append(self.ticket_template['images']) + local_items = [item for sub_items in local_items for item in sub_items] + local_items = map(lambda x: x['image'], local_items) + images_to_download = list(set(items) - set(local_items)) + for image in images_to_download: + api.download(image, IMAGE_DIR) is_new = self.id != installation['id'] new_codes = api.get_codes(self.id) if is_new else None @@ -169,9 +176,6 @@ def update(self): self.scenario = scenario self.ticket_template = ticket_template self._p_changed = True - else: - self.__init__() - self._p_changed = True def get_code(self): # claim a code diff --git a/figureraspbian/settings.py b/figureraspbian/settings.py index edc8652..c3817a7 100644 --- a/figureraspbian/settings.py +++ b/figureraspbian/settings.py @@ -20,8 +20,6 @@ def get_env_setting(setting, default=None): error_msg = "Set the %s env variable" % setting raise ImproperlyConfigured(error_msg) -# Resin IO unique identifier -RESIN_DEVICE_UUID = get_env_setting('RESIN_DEVICE_UUID') # Environment. Dev if local machine. Prod if Raspberry Pi ENVIRONMENT = get_env_setting('ENVIRONMENT', 'development') @@ -72,7 +70,6 @@ def get_env_setting(setting, default=None): def log_config(): - logger.info('RESIN_DEVICE_UUID: %s' % RESIN_DEVICE_UUID) logger.info('ENVIRONMENT: %s' % ENVIRONMENT) logger.info('FIGURE_DIR: %s' % FIGURE_DIR) logger.info('API_HOST: %s' % API_HOST) diff --git a/figureraspbian/tests.py b/figureraspbian/tests.py index ba19f15..9c91513 100644 --- a/figureraspbian/tests.py +++ b/figureraspbian/tests.py @@ -1,5 +1,5 @@ # -*- coding: utf8 -*- - +from copy import deepcopy import unittest import os from datetime import datetime @@ -97,63 +97,32 @@ def test_get_installation(self): api should get installation """ installation = api.get_installation() - self.assertTrue('scenario_obj' in installation) + self.assertTrue('scenario' in installation) self.assertTrue('start' in installation) self.assertTrue('end' in installation) self.assertTrue('place' in installation) - def test_get_scenario(self): - """ - api should get scenario - """ - scenario = api.get_scenario('1') - self.assertTrue('name' in scenario) - self.assertTrue('ticket_template' in scenario) - ticket_template = scenario['ticket_template'] - self.assertTrue('images_objects' in ticket_template) - self.assertTrue('text_variables_objects' in ticket_template) - self.assertTrue('image_variables_objects' in ticket_template) - def test_download(self): """ api should correctly download a file """ - downloaded = api.download('static/snapshots/example.jpg', settings.SNAPSHOT_DIR) - self.assertEqual(os.path.basename(downloaded), 'example.jpg') - - def test_download_when_redirect(self): - """ - api should correctly download a file when redirect - """ - downloaded = api.download('snapshots/example/', settings.SNAPSHOT_DIR) + path = os.path.join(settings.MEDIA_ROOT, 'snapshots') + url = os.path.join(settings.API_HOST, 'static/snapshots/example.jpg') + downloaded = api.download(url, path) self.assertEqual(os.path.basename(downloaded), 'example.jpg') - def test_create_random_text_selection(self): - """ - api should create a random text selection - """ - created = api.create_random_text_selection('1', '1') - self.assertIsNotNone(created) - - def test_create_random_image_selection(self): - """ - api should create a random text selection - """ - created = api.create_random_image_selection('1', '1') - self.assertIsNotNone(created) - def test_create_ticket(self): """ api should create ticket """ - snapshot = "%s/resources/2_20150331.jpg" % settings.FIGURE_DIR + snapshot = "%s/media/snapshots/example.jpg" % settings.FIGURE_DIR ticket = snapshot # for testing purposes code = 'JIKO2' dt = datetime.now(pytz.timezone(settings.TIMEZONE)) random_text_selections = [('1', {'id': '1', 'text': 'toto'})] random_image_selections = [] ticket = { - 'installation': '2', + 'installation': '18', 'snapshot': snapshot, 'ticket': ticket, 'dt': dt, @@ -194,8 +163,22 @@ def setUp(self): ] } ], - "image_variables": [], - "images": [] + "image_variables": [{ + "owner": "test@figuredevices.com", + "id": 1, + "name": "Profession", + "items": [ + { + "image": "http://image1" + }, + { + "image": "http://image2" + } + ] + }], + "images": [ + {"image": "http://image3"}, + {"image": "http://image4"}] } }, "place": None, @@ -237,8 +220,7 @@ def test_update_installation(self): api.download = MagicMock() api.get_installation = MagicMock(return_value=self.mock_installation) api.get_codes = MagicMock(return_value=self.mock_codes) - database = Database() - with managed(database) as db: + with managed(Database()) as db: self.assertIsNone(db.data.installation.id) db.update_installation() installation = db.data.installation @@ -248,8 +230,32 @@ def test_update_installation(self): self.assertEqual(installation.scenario['name'], 'Marabouts') self.assertIsNotNone(installation.ticket_template) self.assertEqual(installation.codes, self.mock_codes) + calls = [call("http://image4", os.path.join(settings.MEDIA_ROOT, 'images')), + call("http://image1", os.path.join(settings.MEDIA_ROOT, 'images')), + call("http://image2", os.path.join(settings.MEDIA_ROOT, 'images')), + call("http://image3", os.path.join(settings.MEDIA_ROOT, 'images'))] + api.download.assert_has_calls(calls) + with managed(Database()) as db: self.assertEqual(db.data.installation.codes, self.mock_codes) + api.download = MagicMock() + db.update_installation() + self.assertFalse(api.download.called) + mock_installation = deepcopy(self.mock_installation) + mock_installation['scenario']['ticket_template']['image_variables'] = [{ + "owner": "test@figuredevices.com", + "id": 1, + "name": "Profession", + "items": [ + { + "image": "http://image5" + }]}] + mock_installation['scenario']['ticket_template']['images'] = [{"image": "http://image6"}] + api.get_installation = MagicMock(return_value=mock_installation) + db.update_installation() + calls = [call("http://image5", os.path.join(settings.MEDIA_ROOT, 'images')), + call("http://image6", os.path.join(settings.MEDIA_ROOT, 'images'))] + api.download.assert_has_calls(calls) def test_get_installation_return_none(self): """ @@ -464,15 +470,6 @@ def test_processus(self): self.assertEqual(len(db.dbroot['tickets']._tickets.items()), 1) -class TestPhantomJS(unittest.TestCase): - - def test_save_screenshot(self): - """ - save_screenshot should make a screenshot of ticket.html and save it to a file - """ - phantomjs.save_screenshot('./media/tickets/test.jpg') - - if __name__ == '__main__': unittest.main() From 699fd90214d5c0f548917255bb4d0d69881fa5b4 Mon Sep 17 00:00:00 2001 From: benoitguigal Date: Tue, 2 Jun 2015 10:42:27 +0200 Subject: [PATCH 9/9] fix get_codes --- figureraspbian/db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/figureraspbian/db.py b/figureraspbian/db.py index 08deb53..10325b1 100644 --- a/figureraspbian/db.py +++ b/figureraspbian/db.py @@ -166,7 +166,7 @@ def update(self): for image in images_to_download: api.download(image, IMAGE_DIR) is_new = self.id != installation['id'] - new_codes = api.get_codes(self.id) if is_new else None + new_codes = api.get_codes(installation['id']) if is_new else None if new_codes: self.codes = new_codes