diff --git a/pyexchange/base/calendar.py b/pyexchange/base/calendar.py index 36bc920..75bfa5d 100644 --- a/pyexchange/base/calendar.py +++ b/pyexchange/base/calendar.py @@ -302,6 +302,10 @@ def conference_room(self): if self.resources and len(self.resources) == 1: return self.resources[0] + @property + def attachments(self): + return self._attachments + def validate(self): """ Validates that all required fields are present """ if not self.start: diff --git a/pyexchange/exchange2010/__init__.py b/pyexchange/exchange2010/__init__.py index 3862e19..3ce0ccc 100644 --- a/pyexchange/exchange2010/__init__.py +++ b/pyexchange/exchange2010/__init__.py @@ -555,7 +555,6 @@ def _parse_id_and_change_key_from_response(self, response): return None, None def _parse_response_for_get_event(self, response): - result = self._parse_event_properties(response) organizer_properties = self._parse_event_organizer(response) @@ -564,6 +563,9 @@ def _parse_response_for_get_event(self, response): organizer_properties['email'] = None result[u'organizer'] = ExchangeEventOrganizer(**organizer_properties) + attachment_ids = self._parse_event_attachments(response) + result[u'_attachments'] = [Exchange2010Attachment(self.service, attachment_id) for attachment_id in attachment_ids] + attendee_properties = self._parse_event_attendees(response) result[u'_attendees'] = self._build_resource_dictionary([ExchangeEventResponse(**attendee) for attendee in attendee_properties]) @@ -659,6 +661,10 @@ def _parse_event_properties(self, response): return result + def _parse_event_attachments(self, response): + attachments = response.xpath(u'//m:Items/t:CalendarItem/t:Attachments/t:FileAttachment/t:AttachmentId', namespaces=soap_request.NAMESPACES) + return [att.get('Id') for att in attachments] + def _parse_event_organizer(self, response): organizer = response.xpath(u'//m:Items/t:CalendarItem/t:Organizer/t:Mailbox', namespaces=soap_request.NAMESPACES) diff --git a/pyexchange/exchange2010/soap_request.py b/pyexchange/exchange2010/soap_request.py index 8b31187..6c5ac0d 100644 --- a/pyexchange/exchange2010/soap_request.py +++ b/pyexchange/exchange2010/soap_request.py @@ -116,6 +116,7 @@ def get_item(exchange_id, format=u"Default"): ) return root + def get_calendar_items(format=u"Default", calendar_id=u'calendar', start=None, end=None, max_entries=999999, delegate_for=None): start = start.strftime(EXCHANGE_DATETIME_FORMAT) end = end.strftime(EXCHANGE_DATETIME_FORMAT) @@ -405,6 +406,7 @@ def new_event(event): return root + def get_attachment(attachment_id, body_type, include_mime_content, filter_html_content): """ Get attachment https://msdn.microsoft.com/en-us/library/aa494316(v=exchg.140).aspx @@ -435,6 +437,7 @@ def get_attachment(attachment_id, body_type, include_mime_content, filter_html_c ) ) + def delete_attachment(event_id): """ @@ -458,6 +461,7 @@ def delete_attachment(event_id): ) ) + def create_attachment(event, file_name, data): """ Create attachment https://msdn.microsoft.com/en-us/library/aa565877(v=exchg.140).aspx @@ -484,6 +488,7 @@ def create_attachment(event, file_name, data): ) ) + def delete_event(event): """ diff --git a/tests/exchange2010/fixtures.py b/tests/exchange2010/fixtures.py index e9afbb1..6634e71 100644 --- a/tests/exchange2010/fixtures.py +++ b/tests/exchange2010/fixtures.py @@ -206,6 +206,8 @@ RESOURCE_WITH_NO_EMAIL_ADDRESS = ExchangeEventResponse(name=u'I am also bad', email=None, required=True, response=RESPONSE_UNKNOWN, last_response=None) +ATTACHMENT_IDS = [u'ɱăğĭč čăřρĕt', u'flÿïnġ dïnösäür'] + GET_ITEM_RESPONSE = u""" @@ -397,6 +399,209 @@ conflict_event=TEST_CONFLICT_EVENT, ) +GET_ITEM_WITH_ATTACHMENTS_RESPONSE = u""" + + + + + + + + NoError + + + + + IPM.Appointment + {event.subject} + Normal + {event.body} + {event.body} + + + + Some filename.txt + + + + Some filename.txt + + + {event.start:%Y-%m-%dT%H:%M:%SZ} + 1935 + Normal + false + false + false + false + false + {event.start:%Y-%m-%dT%H:%M:%SZ} + {event.start:%Y-%m-%dT%H:%M:%SZ} + + + + + {event.start:%Y-%m-%dT%H:%M:%SZ} + true + 15 + + + false + en-US + {event.start:%Y-%m-%dT%H:%M:%SZ} + {event.end:%Y-%m-%dT%H:%M:%SZ} + false + Busy + {event.location} + true + false + false + false + true + Single + Organizer + + + {organizer.name} + {organizer.email} + SMTP + + + + + + {required_accepted.name} + {required_accepted.email} + SMTP + + {required_accepted.response} + {required_accepted.last_response:%Y-%m-%dT%H:%M:%SZ} + + + + {required_tentative.name} + {required_tentative.email} + SMTP + + {required_tentative.response} + {required_tentative.last_response:%Y-%m-%dT%H:%M:%SZ} + + + + {required_declined.name} + {required_declined.email} + SMTP + + {required_declined.response} + {required_declined.last_response:%Y-%m-%dT%H:%M:%SZ} + + + + {required_unknown.name} + {required_unknown.email} + SMTP + + {required_unknown.response} + + + + + + {optional_accepted.name} + {optional_accepted.email} + SMTP + + {optional_accepted.response} + {optional_accepted.last_response:%Y-%m-%dT%H:%M:%SZ} + + + + {optional_tentative.name} + {optional_tentative.email} + SMTP + + {optional_tentative.response} + {optional_tentative.last_response:%Y-%m-%dT%H:%M:%SZ} + + + + {optional_declined.name} + {optional_declined.email} + SMTP + + {optional_declined.response} + {optional_declined.last_response:%Y-%m-%dT%H:%M:%SZ} + + + + {optional_unknown.name} + {optional_unknown.email} + SMTP + + {optional_unknown.response} + + + + + + {resource.name} + {resource.email} + SMTP + + {resource.response} + {resource.last_response:%Y-%m-%dT%H:%M:%SZ} + + + + 1 + 1 + + + + {conflict_event.subject} + {conflict_event.start:%Y-%m-%dT%H:%M:%SZ} + {conflict_event.end:%Y-%m-%dT%H:%M:%SZ} + Busy + {conflict_event.location} + + + + + + my other OTHER awesome event + {event.start:%Y-%m-%dT%H:%M:%SZ} + {event.end:%Y-%m-%dT%H:%M:%SZ} + Busy + Outside + + + PT1H + (UTC-08:00) Pacific Time (US & Canada) + 0 + 1 + + + + + + + +""".format(event=TEST_EVENT, + organizer=ORGANIZER, + required_accepted=PERSON_REQUIRED_ACCEPTED, + required_tentative=PERSON_REQUIRED_TENTATIVE, + required_declined=PERSON_REQUIRED_DECLINED, + required_unknown=PERSON_REQUIRED_UNKNOWN, + optional_accepted=PERSON_OPTIONAL_ACCEPTED, + optional_tentative=PERSON_OPTIONAL_TENTATIVE, + optional_declined=PERSON_OPTIONAL_DECLINED, + optional_unknown=PERSON_OPTIONAL_UNKNOWN, + resource=RESOURCE, + conflict_event=TEST_CONFLICT_EVENT, + attachment_id1=ATTACHMENT_IDS[0], + attachment_id2=ATTACHMENT_IDS[1] + ) + CONFLICTING_EVENTS_RESPONSE = u""" diff --git a/tests/exchange2010/test_get_attachment.py b/tests/exchange2010/test_get_attachment.py index e69e561..ebca051 100644 --- a/tests/exchange2010/test_get_attachment.py +++ b/tests/exchange2010/test_get_attachment.py @@ -12,19 +12,6 @@ from pyexchange.connection import ExchangeNTLMAuthConnection from pyexchange.exceptions import * # noqa -import logging -logger = logging.getLogger("pyexchange") -logger.setLevel(logging.DEBUG) -formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') -ch = logging.StreamHandler() -ch.setLevel(logging.DEBUG) -ch.setFormatter(formatter) -logger.addHandler(ch) -fh = logging.FileHandler('test.log') -fh.setLevel(logging.DEBUG) -fh.setFormatter(formatter) -logger.addHandler(fh) - from .fixtures import * # noqa config = { diff --git a/tests/exchange2010/test_get_event_with_attachments.py b/tests/exchange2010/test_get_event_with_attachments.py new file mode 100644 index 0000000..6e00c0c --- /dev/null +++ b/tests/exchange2010/test_get_event_with_attachments.py @@ -0,0 +1,65 @@ +""" +(c) 2013 LinkedIn Corp. All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License");?you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software?distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" +import unittest +from httpretty import HTTPretty, httprettified +import httpretty +from pyexchange import Exchange2010Service + +from pyexchange.exchange2010 import Exchange2010Attachment +from pyexchange.connection import ExchangeNTLMAuthConnection +from pyexchange.exceptions import * # noqa + +from .fixtures import * # noqa + +config = { + "username": FAKE_EXCHANGE_USERNAME, + "password": FAKE_EXCHANGE_PASSWORD, + "url": FAKE_EXCHANGE_URL +} + + +class Test_GettingAnEventWithAttachment(unittest.TestCase): + + @httprettified + def setUp(self): + self.calendar = Exchange2010Service( + connection=ExchangeNTLMAuthConnection( + **config + ) + ).calendar() + + def test_sets_up_an_empty_array_if_no_attachments(self): + httpretty.enable() + HTTPretty.register_uri( + HTTPretty.POST, + FAKE_EXCHANGE_URL, + body=GET_ITEM_RESPONSE.encode('utf-8'), + content_type='text/xml; charset=utf-8' + ) + + event = self.calendar.get_event(id=TEST_EVENT.id) + + assert event.attachments.__len__() == 0 + + def test_sets_up_correct_attachment_objects(self): + httpretty.enable() + HTTPretty.register_uri( + HTTPretty.POST, + FAKE_EXCHANGE_URL, + body=GET_ITEM_WITH_ATTACHMENTS_RESPONSE.encode('utf-8'), + content_type='text/xml; charset=utf-8' + ) + + event = self.calendar.get_event(id=TEST_EVENT.id) + + # Two, as per fixture + assert event.attachments.__len__() == 2 + # Of type Attachment object + for x in event.attachments: + assert type(x) == Exchange2010Attachment + # With the correct ids + assert set(ATTACHMENT_IDS) == set([x.id for x in event.attachments])