Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
07d72d2
Add IDE config folder to .gitignore
Tomasz-Kluczkowski Jun 1, 2018
e790cbc
Modify initial requirements for development
Tomasz-Kluczkowski Jun 1, 2018
dab222d
Run tox with requirements_dev.txt and confirm tests pass initially.
Tomasz-Kluczkowski Jun 1, 2018
0af0480
Add get_attachment_type method to CallbackEvent.
Tomasz-Kluczkowski Jun 1, 2018
89f255b
Add new requirements to requirements_dev.txt.
Tomasz-Kluczkowski Jun 1, 2018
52434f2
Add python-magic to deps in tox.ini.
Tomasz-Kluczkowski Jun 1, 2018
f313368
Add sample json data for tests of get_attachment_type method.
Tomasz-Kluczkowski Jun 1, 2018
a6e2afc
Add test for get_attachment_type.
Tomasz-Kluczkowski Jun 1, 2018
bd35471
Modify add_callback method in Webhook to save attachment type.
Tomasz-Kluczkowski Jun 1, 2018
daaa58e
Add test for add_callback method to confirm attachment type is being …
Tomasz-Kluczkowski Jun 1, 2018
53664f6
Create custom template filter in test_app to display attached image.
Tomasz-Kluczkowski Jun 1, 2018
2469f32
Add tests for new custom filter.
Tomasz-Kluczkowski Jun 1, 2018
c066137
Add tests for new custom filter.
Tomasz-Kluczkowski Jun 1, 2018
e00cc50
Override default addAttachmentToCard.html.
Tomasz-Kluczkowski Jun 1, 2018
0c8c792
Add tests of rendering the overridden addAttachmentToCard.html template.
Tomasz-Kluczkowski Jun 1, 2018
5b12643
Add test_app to INSTALLED_APPS setting in test_settings.py.
Tomasz-Kluczkowski Jun 1, 2018
451debf
Add admin method to add missing attachment types of existing Callback…
Tomasz-Kluczkowski Jun 1, 2018
322d95c
Test add_attachment_type admin method.
Tomasz-Kluczkowski Jun 1, 2018
41a41fc
Increase test coverage.
Tomasz-Kluczkowski Jun 1, 2018
90bd99f
Lint application using flake8.
Tomasz-Kluczkowski Jun 1, 2018
938ee79
Remove code style issues reported by flake8 from hipchat.py.
Tomasz-Kluczkowski Jun 1, 2018
cd78a2c
Remove code style issues reported by flake8 from trello_webhooks/urls…
Tomasz-Kluczkowski Jun 1, 2018
5ad252e
Remove code style issues reported by flake8 from trello_webhooks/admi…
Tomasz-Kluczkowski Jun 1, 2018
79bb269
Remove code style issues reported by flake8 from trello_webhooks/test…
Tomasz-Kluczkowski Jun 1, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[flake8]
exclude =
*migrations*
.tox/
max-line-length = 100
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,5 @@ coverage.xml
docs/_build/

test_app/static
.idea/
coverage_reports/
7 changes: 7 additions & 0 deletions requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
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
python-magic==0.4.15
requests==2.18.4
12 changes: 6 additions & 6 deletions test_app/hipchat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
7 changes: 7 additions & 0 deletions test_app/templates/trello_webhooks/addAttachmentToCard.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% load test_app_tags %}
{% if action.data.attachment.attachmentType|is_image %}
<strong>{{action.memberCreator.initials}}</strong> added attachment "<strong><a href="{{action.data.attachment.url}}"><img src="{{action.data.attachment.url}}" alt="Attachment"></a></strong>"
{% else %}
<strong>{{action.memberCreator.initials}}</strong> added attachment "<strong><a href="{{action.data.attachment.url}}">{{action.data.attachment.name}}</a></strong>"
{% endif %}
{% include 'trello_webhooks/partials/card_link.html' %}
Empty file.
20 changes: 20 additions & 0 deletions test_app/templatetags/test_app_tags.py
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion test_app/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Empty file added test_app/tests/__init__.py
Empty file.
17 changes: 17 additions & 0 deletions test_app/tests/test_templatetags.py
Original file line number Diff line number Diff line change
@@ -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))
3 changes: 2 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ deps =
mock==1.0.1
coverage==3.7.1
django-nose==1.2
-rrequirements.txt
python-magic==0.4.15
-rrequirements_dev.txt
38 changes: 38 additions & 0 deletions trello_webhooks/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -163,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)
25 changes: 24 additions & 1 deletion trello_webhooks/models.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -225,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
Expand Down Expand Up @@ -347,3 +353,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
44 changes: 44 additions & 0 deletions trello_webhooks/tests/sample_data/addAttachmentToCard.json
Original file line number Diff line number Diff line change
@@ -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": []
}
Original file line number Diff line number Diff line change
@@ -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": []
}
40 changes: 40 additions & 0 deletions trello_webhooks/tests/test_admin.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -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)
Loading