From 07d72d2b97b88094d8bdbfbe0ee64e8f6da9309c Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 09:52:20 +0200 Subject: [PATCH 01/24] Add IDE config folder to .gitignore Added .idea/ to .gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 72895fb..7637cd4 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,4 @@ coverage.xml docs/_build/ test_app/static +.idea/ \ No newline at end of file From e790cbcdbf8525e076496d8aa026d929114d68d5 Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 09:59:05 +0200 Subject: [PATCH 02/24] Modify initial requirements for development Added requirements_dev.txt with initial requirements. Had to change psycopg2 version to 2.7.4 as it was not compatible with postgres version. --- requirements_dev.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 requirements_dev.txt diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 0000000..d2e757b --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1,5 @@ +Django==1.7.1 +dj-database-url==0.3.0 +django-jsonfield==0.9.13 +git+https://github.com/sarumont/py-trello.git@766c90dc1dacd2e3fcfc579079a6aab38be43aef +psycopg2==2.7.4 From dab222df52db9ec9462290a27dd37c316638c04c Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 10:06:15 +0200 Subject: [PATCH 03/24] Run tox with requirements_dev.txt and confirm tests pass initially. Changed deps / requirements.txt to requirements_dev.txt and run tox. Added coverage_reports folder produced by tox to .gitignore. --- .gitignore | 3 ++- tox.ini | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 7637cd4..747d937 100644 --- a/.gitignore +++ b/.gitignore @@ -53,4 +53,5 @@ coverage.xml docs/_build/ test_app/static -.idea/ \ No newline at end of file +.idea/ +coverage_reports/ \ No newline at end of file diff --git a/tox.ini b/tox.ini index 6995894..7fac3b4 100644 --- a/tox.ini +++ b/tox.ini @@ -17,4 +17,4 @@ deps = mock==1.0.1 coverage==3.7.1 django-nose==1.2 - -rrequirements.txt + -rrequirements_dev.txt From 0af04805dea72bb11cdd707ef9a1e76cbc9e9453 Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 10:58:00 +0200 Subject: [PATCH 04/24] Add get_attachment_type method to CallbackEvent. Added get_attachment_type method to Callbackevent. We are using requests to obtain the initial chunk of the file (preventing downloading the entire attachment). New dependencies were added - requests and python-magic which helps to get the mime type. My assumption is that the attachment's mime type signature is within the first 1024 bytes fo the file (seems to be the case for almost all files but .iso). --- trello_webhooks/models.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/trello_webhooks/models.py b/trello_webhooks/models.py index 6c13671..047e065 100755 --- a/trello_webhooks/models.py +++ b/trello_webhooks/models.py @@ -1,6 +1,8 @@ # # -*- coding: utf-8 -*- import json import logging +import requests +import magic from django.core.urlresolvers import reverse from django.db import models @@ -347,3 +349,20 @@ def render(self): self.template ) return None + + def get_attachment_type(self): + """Returns attachment's file type. + + Returns mime type of the attached file or none if attachment not present. + + """ + mime = None + attachment_url = self.action_data.get('attachment', {}).get('url') + if attachment_url is None: + return None + with requests.get(attachment_url, stream=True) as response: + for chunk in response.iter_content(chunk_size=1024): + if chunk: + mime = magic.from_buffer(chunk, mime=True) + break + return mime From 89f255bc0d5865f1b1efb0cf3f9663c5b2f30bb3 Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 11:00:38 +0200 Subject: [PATCH 05/24] Add new requirements to requirements_dev.txt. Added python-magic and requests to requirements_dev.txt. --- requirements_dev.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements_dev.txt b/requirements_dev.txt index d2e757b..d312c1f 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -3,3 +3,5 @@ dj-database-url==0.3.0 django-jsonfield==0.9.13 git+https://github.com/sarumont/py-trello.git@766c90dc1dacd2e3fcfc579079a6aab38be43aef psycopg2==2.7.4 +python-magic==0.4.15 +requests==2.18.4 From 52434f29c2321f009a0054948a66d6e53001bae8 Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 11:02:48 +0200 Subject: [PATCH 06/24] Add python-magic to deps in tox.ini. Added python-magic==0.4.15 to tox.ini and ran basic tests to confirm base code works after adding get_attachment_type method to CallbackEvent. --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 7fac3b4..9e9acbc 100644 --- a/tox.ini +++ b/tox.ini @@ -17,4 +17,5 @@ deps = mock==1.0.1 coverage==3.7.1 django-nose==1.2 + python-magic==0.4.15 -rrequirements_dev.txt From f3133681da70fe301034cc29a632759e22e76d46 Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 11:19:20 +0200 Subject: [PATCH 07/24] Add sample json data for tests of get_attachment_type method. Added addAttachmentToCard.json - json file produced by Trello with txt attachment. Added addAttachmentToCardImageType.json - json file produced by Trello with image attachment. --- .../sample_data/addAttachmentToCard.json | 44 ++++++++++++++++++ .../addAttachmentToCardImageType.json | 46 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 trello_webhooks/tests/sample_data/addAttachmentToCard.json create mode 100644 trello_webhooks/tests/sample_data/addAttachmentToCardImageType.json diff --git a/trello_webhooks/tests/sample_data/addAttachmentToCard.json b/trello_webhooks/tests/sample_data/addAttachmentToCard.json new file mode 100644 index 0000000..498dbd4 --- /dev/null +++ b/trello_webhooks/tests/sample_data/addAttachmentToCard.json @@ -0,0 +1,44 @@ +{ + "action": + { + "id": "5b0d1ad9cdb0072bef60289a", + "idMemberCreator": "5b0a8af4fdabbc0e589f4494", + "data": { + "board": { + "shortLink": "6ZNqzWHc", + "name": "my_test_board", + "id": "5b0a8b38d3bd63f8365d9751" + }, + "list": { + "name": "To Do", + "id": "5b0a8b38d3bd63f8365d9752" + }, + "card": { + "shortLink": "vx3BSWX2", + "idShort": 3, + "name": "test_card", + "id": "5b0d1ac18ec1210b806d837c" + }, + "attachment": { + "url": "https://trello-attachments.s3.amazonaws.com/5b0a8b38d3bd63f8365d9751/5b0d1ac18ec1210b806d837c/ab4f8943755d458c13660452e24e1051/LICENSE", + "name": "LICENSE", + "id": "5b0d1ad9cdb0072bef602899" + } + }, + "type": "addAttachmentToCard", + "date": "2018-05-29T09:18:17.776Z", + "limits": {}, + "memberCreator": { + "id": "5b0a8af4fdabbc0e589f4494", + "avatarHash": null, + "avatarUrl": null, + "fullName": "Tomasz Kluczkowski", + "initials": "TK", + "username": "tomaszkluczkowski" + } + }, + "checklists": [], + "customFieldItems": [], + "members": [], + "pluginData": [] +} \ No newline at end of file diff --git a/trello_webhooks/tests/sample_data/addAttachmentToCardImageType.json b/trello_webhooks/tests/sample_data/addAttachmentToCardImageType.json new file mode 100644 index 0000000..be9cc58 --- /dev/null +++ b/trello_webhooks/tests/sample_data/addAttachmentToCardImageType.json @@ -0,0 +1,46 @@ +{ + "action": + { + "id": "5b0e661039040319ab1d2418", + "idMemberCreator": "5b0a8af4fdabbc0e589f4494", + "data": { + "board": { + "shortLink": "6ZNqzWHc", + "name": "my_test_board", + "id": "5b0a8b38d3bd63f8365d9751" + }, + "list": { + "name": "To Do", + "id": "5b0a8b38d3bd63f8365d9752" + }, + "card": { + "shortLink": "isnnzRAc", + "idShort": 4, + "name": "test_card_2", + "id": "5b0e65e5d2ece050ee899e66" + }, + "attachment": { + "url": "https://trello-attachments.s3.amazonaws.com/5b0a8b38d3bd63f8365d9751/5b0e65e5d2ece050ee899e66/bfefa10c250d785a1a78c6f0e53ac398/avatar-of-a-person-with-dark-short-hair.png", + "name": "avatar-of-a-person-with-dark-short-hair.png", + "id": "5b0e661039040319ab1d2416", + "previewUrl": "https://trello-attachments.s3.amazonaws.com/5b0a8b38d3bd63f8365d9751/5b0e65e5d2ece050ee899e66/bfefa10c250d785a1a78c6f0e53ac398/avatar-of-a-person-with-dark-short-hair.png", + "previewUrl2x": "https://trello-attachments.s3.amazonaws.com/5b0a8b38d3bd63f8365d9751/5b0e65e5d2ece050ee899e66/bfefa10c250d785a1a78c6f0e53ac398/avatar-of-a-person-with-dark-short-hair.png" + } + }, + "type": "addAttachmentToCard", + "date": "2018-05-30T08:51:28.687Z", + "limits": {}, + "memberCreator": { + "id": "5b0a8af4fdabbc0e589f4494", + "avatarHash": null, + "avatarUrl": null, + "fullName": "Tomasz Kluczkowski", + "initials": "TK", + "username": "tomaszkluczkowski" + } + }, + "checklists": [], + "customFieldItems": [], + "members": [], + "pluginData": [] +} \ No newline at end of file From a6e2afc40e957ffc0de187213d340919e98e61c5 Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 11:58:02 +0200 Subject: [PATCH 08/24] Add test for get_attachment_type. Added tests for get_attachment_type method in test_models.py. Had to mock out response object and file object to avoid relying on the internet connection. This obviously means that the attachment types are fed manually through use of mocks and we only test the logic of the get_attachment_type method not the actual working of the get / from_buffer. --- trello_webhooks/tests/test_models.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/trello_webhooks/tests/test_models.py b/trello_webhooks/tests/test_models.py index eb0b2fe..371866d 100644 --- a/trello_webhooks/tests/test_models.py +++ b/trello_webhooks/tests/test_models.py @@ -39,6 +39,17 @@ def mock_trello_sync(webhook, verb): return webhook +class MockResponse(): + def iter_content(self, chunk_size): + return [1, 2] + + def __exit__(self, exc_type, exc_val, exc_tb): + pass + + def __enter__(self): + return self + + def mock_trello_sync_x(webhook, verb): """Fake version of the Webhook._trello_sync method that mimics failure. @@ -315,3 +326,15 @@ def test_card_name(self): self.assertEqual(ce.card_name, None) ce.event_payload = get_sample_data('createCard', 'text') self.assertEqual(ce.card_name, ce.event_payload['action']['data']['card']['name']) # noqa + + def test_get_attachment_type_no_attachment(self): + ce = CallbackEvent() + ce.event_payload = get_sample_data('commentCard', 'json') + self.assertEqual(ce.get_attachment_type(), None) + + @mock.patch('trello_webhooks.models.requests.get', return_value=MockResponse()) + @mock.patch('trello_webhooks.models.magic.from_buffer', return_value='image/png') + def test_get_attachment_type_with_image_attachment(self, mock_from_buffer, mock_response): + ce = CallbackEvent() + ce.event_payload = get_sample_data('addAttachmentToCardImageType', 'json') + self.assertEqual(ce.get_attachment_type(), 'image/png') From bd35471dbd794dfff1f1a649c5fb87e5ab77d341 Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 12:22:03 +0200 Subject: [PATCH 09/24] Modify add_callback method in Webhook to save attachment type. Added logic confirming if event's payload data contains an attachment key. If so the attachment type is added to payload using CallbackEvent's get_attachment_type method and then event object is saved to the database. --- trello_webhooks/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/trello_webhooks/models.py b/trello_webhooks/models.py index 047e065..22dc075 100755 --- a/trello_webhooks/models.py +++ b/trello_webhooks/models.py @@ -227,7 +227,11 @@ def add_callback(self, body_text): webhook=self, event_type=action, event_payload=body_text - ).save() + ) + if payload.get('action').get('data').get('attachment', None): + attachment_type = event.get_attachment_type() + event.event_payload['action']['data']['attachment']['attachmentType'] = attachment_type + event.save() self.touch() signals.callback_received.send(sender=self.__class__, event=event) return event From daaa58efa3d6554ffe94167dc025aa498870954a Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 13:06:01 +0200 Subject: [PATCH 10/24] Add test for add_callback method to confirm attachment type is being saved. Added test confirming that for events with attachment present the attachment type is properly saved in the event's payload data. To avoid connecting to the internet get_attachment_type is mocked out. --- trello_webhooks/tests/test_models.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/trello_webhooks/tests/test_models.py b/trello_webhooks/tests/test_models.py index 371866d..1d53ff2 100644 --- a/trello_webhooks/tests/test_models.py +++ b/trello_webhooks/tests/test_models.py @@ -264,6 +264,15 @@ def test_add_callback(self): self.assertEqual(event.event_payload, payload) # other CallbackEvent properties are tested in CallbackEvent tests + @mock.patch('trello_webhooks.models.CallbackEvent.get_attachment_type', + return_value='text/plain') + def test_add_callback_saves_attachment_type(self, mock_get_attachment_type): + hook = Webhook().save(sync=False) + payload = get_sample_data('addAttachmentToCard', 'json') + event = hook.add_callback(json.dumps(payload)) + self.assertEqual( + event.event_payload['action']['data']['attachment']['attachmentType'], 'text/plain') + class CallbackEventModelTest(TestCase): From 53664f6bdad05a2d566215603b2c5adf82f3cc8a Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 13:24:29 +0200 Subject: [PATCH 11/24] Create custom template filter in test_app to display attached image. Added templatetags folder to test_app with: __init_.py - standard for packages test_app_tags.py - file containing new filter. Added is_image filter which takes value passed from the template and returns a bool (True for image attachments, false otherwise.) --- test_app/templatetags/__init__.py | 0 test_app/templatetags/test_app_tags.py | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 test_app/templatetags/__init__.py create mode 100644 test_app/templatetags/test_app_tags.py diff --git a/test_app/templatetags/__init__.py b/test_app/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test_app/templatetags/test_app_tags.py b/test_app/templatetags/test_app_tags.py new file mode 100644 index 0000000..5eca29a --- /dev/null +++ b/test_app/templatetags/test_app_tags.py @@ -0,0 +1,20 @@ +from django import template + +register = template.Library() + + +@register.filter +def is_image(value): + """Confirm if attachment is an image inside a template. + + Args: + value: string, result of getting a mime type of the attachment file + + Returns: bool, True if attachment's mime type is an image, False otherwise. + + """ + if value: + attachment_spec = value.split("/") + return attachment_spec[0] == "image" + else: + return False From 2469f32fc7c03b9a6f9c852255661b371d3a20bc Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 13:28:16 +0200 Subject: [PATCH 12/24] Add tests for new custom filter. Created tests folder in test_app with: __init__.py - standard for packages test_templatetags.py - file to put in template tags' tests for test_app. --- test_app/tests/__init__.py | 0 test_app/tests/test_templatetags.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 test_app/tests/__init__.py create mode 100644 test_app/tests/test_templatetags.py diff --git a/test_app/tests/__init__.py b/test_app/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test_app/tests/test_templatetags.py b/test_app/tests/test_templatetags.py new file mode 100644 index 0000000..e69de29 From c066137d3dabad02f6d9baedbce107e08f67e787 Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 13:48:01 +0200 Subject: [PATCH 13/24] Add tests for new custom filter. Created tests folder in test_app with: __init__.py - standard for packages test_templatetags.py - file to put in template tags' tests for test_app. --- test_app/tests/test_templatetags.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test_app/tests/test_templatetags.py b/test_app/tests/test_templatetags.py index e69de29..bbdab2f 100644 --- a/test_app/tests/test_templatetags.py +++ b/test_app/tests/test_templatetags.py @@ -0,0 +1,17 @@ +from django.test import TestCase + +from test_app.templatetags.test_app_tags import is_image + + +class TemplateTagsTests(TestCase): + + def test_is_image_with_image_mime(self): + mime = 'image/jpg' + self.assertTrue(is_image(mime)) + + def test_is_image_with_non_image_mime(self): + mime = 'text/plain' + self.assertFalse(is_image(mime)) + + def test_is_image_with_None(self): + self.assertFalse(is_image(None)) From e00cc50ef655022f0582137fc05ae4cf88c2768c Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 13:56:39 +0200 Subject: [PATCH 14/24] Override default addAttachmentToCard.html. Created addAttachmentToCard.html in test_app/templates/trello_webhooks which will override the default one in trello_webhooks app. Added conditional displaying of attached image if is_image template filter returns True. --- .../templates/trello_webhooks/addAttachmentToCard.html | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 test_app/templates/trello_webhooks/addAttachmentToCard.html diff --git a/test_app/templates/trello_webhooks/addAttachmentToCard.html b/test_app/templates/trello_webhooks/addAttachmentToCard.html new file mode 100644 index 0000000..f37e500 --- /dev/null +++ b/test_app/templates/trello_webhooks/addAttachmentToCard.html @@ -0,0 +1,7 @@ +{% load test_app_tags %} +{% if action.data.attachment.attachmentType|is_image %} +{{action.memberCreator.initials}} added attachment "Attachment" +{% else %} +{{action.memberCreator.initials}} added attachment "{{action.data.attachment.name}}" +{% endif %} +{% include 'trello_webhooks/partials/card_link.html' %} \ No newline at end of file From 0c8c7922c4e01f4af9ef8c5a211cbe3ff9a305e5 Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 14:33:42 +0200 Subject: [PATCH 15/24] Add tests of rendering the overridden addAttachmentToCard.html template. Added tests of CallbackEvent render method for modified addAttachmentToCard.html template with image attachment and text attachment. --- trello_webhooks/tests/test_models.py | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/trello_webhooks/tests/test_models.py b/trello_webhooks/tests/test_models.py index 1d53ff2..2f8e59d 100644 --- a/trello_webhooks/tests/test_models.py +++ b/trello_webhooks/tests/test_models.py @@ -347,3 +347,36 @@ def test_get_attachment_type_with_image_attachment(self, mock_from_buffer, mock_ ce = CallbackEvent() ce.event_payload = get_sample_data('addAttachmentToCardImageType', 'json') self.assertEqual(ce.get_attachment_type(), 'image/png') + + def test_render_of_AddAttachmentToCard_with_image_attachment(self): + ce = CallbackEvent() + ce.event_payload = get_sample_data('addAttachmentToCardImageType', 'json') + ce.event_payload['action']['data']['attachment']['attachmentType'] = u'image/png' + ce.event_type = 'addAttachmentToCard' + # Render_to_string replaces all Django logic template tags {% if %} etc with \n characters. + # So they have to be in the test output. + rendered_to_string = ( + u"""\n\nTK added attachment "Attachment""" + u""""\n\n
my_test_board / To Do / test_card_2""") + self.assertEqual(ce.render(), rendered_to_string) + + def test_render_of_AddAttachmentToCard_with_text_attachment(self): + ce = CallbackEvent() + ce.event_payload = get_sample_data('addAttachmentToCard', 'json') + ce.event_type = 'addAttachmentToCard' + ce.event_payload['action']['data']['attachment']['attachmentType'] = u'plain/text' + # Render_to_string replaces all Django logic template tags {% if %} etc with \n characters. + # So they have to be in the test output. + rendered_to_string = ( + u"""\n\nTK added attachment "LICENSE"\n\n
""" + u"""my_test_board / To Do / test_card""" + u"""""") + self.assertEqual(ce.render(), rendered_to_string) From 5b12643e28a698a5153c97bf284f631d79e1d986 Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 14:40:44 +0200 Subject: [PATCH 16/24] Add test_app to INSTALLED_APPS setting in test_settings.py. To override template also during the test test_app must be above trello_webhooks in the list of installed apps. --- test_app/test_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_app/test_settings.py b/test_app/test_settings.py index 7fb7e05..3c69a69 100644 --- a/test_app/test_settings.py +++ b/test_app/test_settings.py @@ -11,7 +11,7 @@ } # the django apps aren't required for the tests, -INSTALLED_APPS = ('trello_webhooks',) +INSTALLED_APPS = ('test_app', 'trello_webhooks',) try: import django_nose # noqa From 451debf8878186d325a8ad33b23710e4eff303ec Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 15:03:44 +0200 Subject: [PATCH 17/24] Add admin method to add missing attachment types of existing CallbackEvents. Introduced add_attachment_type admin method for CallbackEvents allowing easily add missing attachment types for events generated in the past. Currently the method assumes only addAttachmentToCard event types contain an attachment and hence the filtering of queryset with that parameter. This can be extended if other events also contain attachments. --- trello_webhooks/admin.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/trello_webhooks/admin.py b/trello_webhooks/admin.py index 9c27bab..9ea8d82 100644 --- a/trello_webhooks/admin.py +++ b/trello_webhooks/admin.py @@ -81,6 +81,43 @@ def rendered(self, instance): def has_template(self, instance): return instance.render() is not None + def add_attachment_type(self, request, queryset): + """Add missing attachment types to selected CallbackEvent objects. + + For all CallbackEvent objects which were created in the past and contain attachment in data + but are missing the attachment type we want to add it to the event_payload. + + Args: + request: http request object, passed in from the admin view + queryset: a django queryset containing CallbackEvent objects selected in the admin view + + """ + count = queryset.count() + if count == 0: + return + queryset_filtered = queryset.filter(event_type='addAttachmentToCard') + + count = 0 + for event in queryset_filtered: + try: + attachment_type = ( + event.event_payload['action']['data']['attachment']['attachmentType']) + except KeyError: + attachment_type = None + if not attachment_type: + count += 1 + attachment_type = event.get_attachment_type() + event.event_payload['action']['data']['attachment'][ + 'attachmentType'] = attachment_type + event.save(update_fields=['event_payload']) + logger.info( + u"%s added attachment type to %i CallbackEvents from the admin site.", + request.user, count + ) + + add_attachment_type.short_description = "Add missing attachment types" + actions = [add_attachment_type] + class CallbackEventInline(admin.StackedInline): model = CallbackEvent From 322d95ca6a97c2c01b2b21935d6c83e2ad48a88c Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 15:15:25 +0200 Subject: [PATCH 18/24] Test add_attachment_type admin method. Added test for add_attachment admin method in test_admin.py. --- trello_webhooks/tests/test_admin.py | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/trello_webhooks/tests/test_admin.py b/trello_webhooks/tests/test_admin.py index 13a8a8c..778aa56 100644 --- a/trello_webhooks/tests/test_admin.py +++ b/trello_webhooks/tests/test_admin.py @@ -1,8 +1,21 @@ # -*- coding: utf-8 -*- +import mock from django.test import TestCase from trello_webhooks.admin import CallbackEventAdmin from trello_webhooks.models import Webhook, CallbackEvent +from trello_webhooks.tests import get_sample_data + + +class MockResponse(): + def iter_content(self, chunk_size): + return [1, 2] + + def __exit__(self, exc_type, exc_val, exc_tb): + pass + + def __enter__(self): + return self class CallbackEventAdminTests(TestCase): @@ -30,3 +43,30 @@ def test_rendered(self): self.assertIsNotNone(self.admin.rendered(self.event)) self.event.event_type = "X" self.assertIsNone(self.admin.rendered(self.event)) + + @mock.patch('trello_webhooks.models.requests.get', return_value=MockResponse()) + @mock.patch('trello_webhooks.models.magic.from_buffer', return_value='image/png') + @mock.patch('trello_webhooks.admin.logger') + def test_add_attachment_type(self, mock_logger, mock_from_buffer, mock_response): + mock_request = mock.Mock() + mock_request.user = 'test_user' + # Create a CallbackEvent object with attachment but no attachment type. + hook = Webhook().save(sync=False) + payload = get_sample_data('addAttachmentToCardImageType', 'json') + event = CallbackEvent( + webhook=hook, + event_type='addAttachmentToCard', + event_payload=payload + ) + event.save() + queryset = CallbackEvent.objects.all() + # Run helper function to add the missing attachment type. + self.admin.add_attachment_type(mock_request, queryset) + # Reload event objects from the database after it was updated. + # Since we create one CallbackEvent object in setUp the id of the one we + # need to confirm was changed will be id=2 + event = CallbackEvent.objects.get(id=2) + self.assertEqual( + event.event_payload['action']['data']['attachment']['attachmentType'], 'image/png') + mock_logger.info.assert_called_once_with( + '%s added attachment type to %i CallbackEvents from the admin site.', 'test_user', 1) From 41a41fcbf631d647f57a034c73d99761e85b057c Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 15:43:12 +0200 Subject: [PATCH 19/24] Increase test coverage. Added test of human readable representations of CallbackEvent model. Tested: __unicode__ (with object saved and not), __str__ and __repr__. --- trello_webhooks/tests/test_models.py | 38 ++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/trello_webhooks/tests/test_models.py b/trello_webhooks/tests/test_models.py index 2f8e59d..7f5c16f 100644 --- a/trello_webhooks/tests/test_models.py +++ b/trello_webhooks/tests/test_models.py @@ -282,6 +282,44 @@ def test_default_properties(self): def test_save(self): pass + def test_unicode_with_unsaved_event(self): + hook = Webhook().save(sync=False) + ce = CallbackEvent( + webhook=hook, + event_type='addAttachmentToCard' + ) + self.assertEqual(ce.__unicode__(), + """CallbackEvent: 'addAttachmentToCard' raised by webhook 1.""") + + def test_unicode_with_saved_event(self): + hook = Webhook().save(sync=False) + ce = CallbackEvent( + webhook=hook, + event_type='addAttachmentToCard' + ) + ce.save() + self.assertEqual(ce.__unicode__(), + """CallbackEvent 1: 'addAttachmentToCard' raised by webhook 1.""") + + def test_str(self): + hook = Webhook().save(sync=False) + ce = CallbackEvent( + webhook=hook, + event_type='addAttachmentToCard' + ) + self.assertEqual(ce.__str__(), + """CallbackEvent: 'addAttachmentToCard' raised by webhook 1.""") + + def test_repr(self): + hook = Webhook().save(sync=False) + ce = CallbackEvent( + webhook=hook, + event_type='addAttachmentToCard' + ) + ce.save() + self.assertEqual(ce.__repr__(), + """""") + def test_action_data(self): ce = CallbackEvent() self.assertEqual(ce.action_data, None) From 90bd99f135b458f587a5efa1ad6898136c596387 Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 15:47:42 +0200 Subject: [PATCH 20/24] Lint application using flake8. Added .flake8 - config file for flake8 linter. Used 100 chars maximum line setting as per YunoJuno code style guide online. --- .flake8 | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..4b0ce29 --- /dev/null +++ b/.flake8 @@ -0,0 +1,5 @@ +[flake8] +exclude = + *migrations* + .tox/ +max-line-length = 100 \ No newline at end of file From 938ee794b8523ebab2cd3cf14a71843fe7141c24 Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 15:49:35 +0200 Subject: [PATCH 21/24] Remove code style issues reported by flake8 from hipchat.py. Fixed wrong indentation in send_to_hipchat method. --- test_app/hipchat.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test_app/hipchat.py b/test_app/hipchat.py index 25555b0..761d5c8 100644 --- a/test_app/hipchat.py +++ b/test_app/hipchat.py @@ -7,12 +7,12 @@ def send_to_hipchat( - message, - token=settings.HIPCHAT_API_TOKEN, - room=settings.HIPCHAT_ROOM_ID, - sender="Trello", - color="yellow", - notify=False): + message, + token=settings.HIPCHAT_API_TOKEN, + room=settings.HIPCHAT_ROOM_ID, + sender="Trello", + color="yellow", + notify=False): """ Send a message to HipChat. From cd78a2c1f666801de97d9827881ed4c70ad3f789 Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 15:51:19 +0200 Subject: [PATCH 22/24] Remove code style issues reported by flake8 from trello_webhooks/urls.py. Fixed wrong indentation in urlpatterns. --- trello_webhooks/urls.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/trello_webhooks/urls.py b/trello_webhooks/urls.py index f36052f..2ff55ad 100644 --- a/trello_webhooks/urls.py +++ b/trello_webhooks/urls.py @@ -3,9 +3,9 @@ from trello_webhooks import views urlpatterns = patterns(views, - url( - r'^(?P\w+)/(?P\w+)/$', - views.api_callback, - name="trello_callback_url" - ), -) + url( + r'^(?P\w+)/(?P\w+)/$', + views.api_callback, + name="trello_callback_url" + ), + ) From 5ad252e97b5ee7be1722d00779501b8821c8e257 Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 15:52:59 +0200 Subject: [PATCH 23/24] Remove code style issues reported by flake8 from trello_webhooks/admin.py. Fixed missing 2 empty line separation. --- trello_webhooks/admin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/trello_webhooks/admin.py b/trello_webhooks/admin.py index 9ea8d82..7791d82 100644 --- a/trello_webhooks/admin.py +++ b/trello_webhooks/admin.py @@ -200,5 +200,6 @@ def sync(self, request, queryset): sync.short_description = "Sync with Trello" actions = [sync] + admin.site.register(CallbackEvent, CallbackEventAdmin) admin.site.register(Webhook, WebhookAdmin) From 79bb269af3fcb77306386cb1ead97b6d33e62721 Mon Sep 17 00:00:00 2001 From: Tomasz-Kluczkowski Date: Fri, 1 Jun 2018 15:54:19 +0200 Subject: [PATCH 24/24] Remove code style issues reported by flake8 from trello_webhooks/tests/test_models.py. Removed unused trello import. --- trello_webhooks/tests/test_models.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/trello_webhooks/tests/test_models.py b/trello_webhooks/tests/test_models.py index 7f5c16f..2a55503 100644 --- a/trello_webhooks/tests/test_models.py +++ b/trello_webhooks/tests/test_models.py @@ -6,8 +6,6 @@ from django.core.urlresolvers import reverse from django.test import TestCase -import trello - from trello_webhooks.models import Webhook, CallbackEvent from trello_webhooks.settings import ( TRELLO_API_KEY,