Skip to content

Commit

Permalink
Add attachment objects onto event object if available
Browse files Browse the repository at this point in the history
  • Loading branch information
matiboy committed Apr 30, 2016
1 parent ba1918a commit 00eedf6
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 14 deletions.
4 changes: 4 additions & 0 deletions pyexchange/base/calendar.py
Expand Up @@ -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:
Expand Down
8 changes: 7 additions & 1 deletion pyexchange/exchange2010/__init__.py
Expand Up @@ -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)
Expand All @@ -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])

Expand Down Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions pyexchange/exchange2010/soap_request.py
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -435,6 +437,7 @@ def get_attachment(attachment_id, body_type, include_mime_content, filter_html_c
)
)


def delete_attachment(event_id):
"""
<?xml version="1.0" encoding="utf-8"?>
Expand All @@ -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
Expand All @@ -484,6 +488,7 @@ def create_attachment(event, file_name, data):
)
)


def delete_event(event):
"""
Expand Down
205 changes: 205 additions & 0 deletions tests/exchange2010/fixtures.py
Expand Up @@ -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"""<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<h:ServerVersionInfo xmlns:h="http://schemas.microsoft.com/exchange/services/2006/types" xmlns="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" MajorVersion="14" MinorVersion="2" MajorBuildNumber="328" MinorBuildNumber="11"/>
Expand Down Expand Up @@ -397,6 +399,209 @@
conflict_event=TEST_CONFLICT_EVENT,
)

GET_ITEM_WITH_ATTACHMENTS_RESPONSE = u"""<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<h:ServerVersionInfo xmlns:h="http://schemas.microsoft.com/exchange/services/2006/types" xmlns="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" MajorVersion="14" MinorVersion="2" MajorBuildNumber="328" MinorBuildNumber="11"/>
</s:Header>
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<m:GetItemResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
<m:ResponseMessages>
<m:GetItemResponseMessage ResponseClass="Success">
<m:ResponseCode>NoError</m:ResponseCode>
<m:Items>
<t:CalendarItem>
<t:ItemId Id="{event.id}" ChangeKey="{event.change_key}"/>
<t:ParentFolderId Id="fooo" ChangeKey="bar"/>
<t:ItemClass>IPM.Appointment</t:ItemClass>
<t:Subject>{event.subject}</t:Subject>
<t:Sensitivity>Normal</t:Sensitivity>
<t:Body BodyType="HTML">{event.body}</t:Body>
<t:Body BodyType="Text">{event.body}</t:Body>
<t:Attachments>
<t:FileAttachment>
<t:AttachmentId Id="{attachment_id1}"/>
<t:Name>Some filename.txt</t:Name>
</t:FileAttachment>
<t:FileAttachment>
<t:AttachmentId Id="{attachment_id2}"/>
<t:Name>Some filename.txt</t:Name>
</t:FileAttachment>
</t:Attachments>
<t:DateTimeReceived>{event.start:%Y-%m-%dT%H:%M:%SZ}</t:DateTimeReceived>
<t:Size>1935</t:Size>
<t:Importance>Normal</t:Importance>
<t:IsSubmitted>false</t:IsSubmitted>
<t:IsDraft>false</t:IsDraft>
<t:IsFromMe>false</t:IsFromMe>
<t:IsResend>false</t:IsResend>
<t:IsUnmodified>false</t:IsUnmodified>
<t:DateTimeSent>{event.start:%Y-%m-%dT%H:%M:%SZ}</t:DateTimeSent>
<t:DateTimeCreated>{event.start:%Y-%m-%dT%H:%M:%SZ}</t:DateTimeCreated>
<t:ResponseObjects>
<t:CancelCalendarItem/>
<t:ForwardItem/>
</t:ResponseObjects>
<t:ReminderDueBy>{event.start:%Y-%m-%dT%H:%M:%SZ}</t:ReminderDueBy>
<t:ReminderIsSet>true</t:ReminderIsSet>
<t:ReminderMinutesBeforeStart>15</t:ReminderMinutesBeforeStart>
<t:DisplayCc/>
<t:DisplayTo/>
<t:HasAttachments>false</t:HasAttachments>
<t:Culture>en-US</t:Culture>
<t:Start>{event.start:%Y-%m-%dT%H:%M:%SZ}</t:Start>
<t:End>{event.end:%Y-%m-%dT%H:%M:%SZ}</t:End>
<t:IsAllDayEvent>false</t:IsAllDayEvent>
<t:LegacyFreeBusyStatus>Busy</t:LegacyFreeBusyStatus>
<t:Location>{event.location}</t:Location>
<t:IsMeeting>true</t:IsMeeting>
<t:IsCancelled>false</t:IsCancelled>
<t:IsRecurring>false</t:IsRecurring>
<t:MeetingRequestWasSent>false</t:MeetingRequestWasSent>
<t:IsResponseRequested>true</t:IsResponseRequested>
<t:CalendarItemType>Single</t:CalendarItemType>
<t:MyResponseType>Organizer</t:MyResponseType>
<t:Organizer>
<t:Mailbox>
<t:Name>{organizer.name}</t:Name>
<t:EmailAddress>{organizer.email}</t:EmailAddress>
<t:RoutingType>SMTP</t:RoutingType>
</t:Mailbox>
</t:Organizer>
<t:RequiredAttendees>
<t:Attendee>
<t:Mailbox>
<t:Name>{required_accepted.name}</t:Name>
<t:EmailAddress>{required_accepted.email}</t:EmailAddress>
<t:RoutingType>SMTP</t:RoutingType>
</t:Mailbox>
<t:ResponseType>{required_accepted.response}</t:ResponseType>
<t:LastResponseTime>{required_accepted.last_response:%Y-%m-%dT%H:%M:%SZ}</t:LastResponseTime>
</t:Attendee>
<t:Attendee>
<t:Mailbox>
<t:Name>{required_tentative.name}</t:Name>
<t:EmailAddress>{required_tentative.email}</t:EmailAddress>
<t:RoutingType>SMTP</t:RoutingType>
</t:Mailbox>
<t:ResponseType>{required_tentative.response}</t:ResponseType>
<t:LastResponseTime>{required_tentative.last_response:%Y-%m-%dT%H:%M:%SZ}</t:LastResponseTime>
</t:Attendee>
<t:Attendee>
<t:Mailbox>
<t:Name>{required_declined.name}</t:Name>
<t:EmailAddress>{required_declined.email}</t:EmailAddress>
<t:RoutingType>SMTP</t:RoutingType>
</t:Mailbox>
<t:ResponseType>{required_declined.response}</t:ResponseType>
<t:LastResponseTime>{required_declined.last_response:%Y-%m-%dT%H:%M:%SZ}</t:LastResponseTime>
</t:Attendee>
<t:Attendee>
<t:Mailbox>
<t:Name>{required_unknown.name}</t:Name>
<t:EmailAddress>{required_unknown.email}</t:EmailAddress>
<t:RoutingType>SMTP</t:RoutingType>
</t:Mailbox>
<t:ResponseType>{required_unknown.response}</t:ResponseType>
</t:Attendee>
</t:RequiredAttendees>
<t:OptionalAttendees>
<t:Attendee>
<t:Mailbox>
<t:Name>{optional_accepted.name}</t:Name>
<t:EmailAddress>{optional_accepted.email}</t:EmailAddress>
<t:RoutingType>SMTP</t:RoutingType>
</t:Mailbox>
<t:ResponseType>{optional_accepted.response}</t:ResponseType>
<t:LastResponseTime>{optional_accepted.last_response:%Y-%m-%dT%H:%M:%SZ}</t:LastResponseTime>
</t:Attendee>
<t:Attendee>
<t:Mailbox>
<t:Name>{optional_tentative.name}</t:Name>
<t:EmailAddress>{optional_tentative.email}</t:EmailAddress>
<t:RoutingType>SMTP</t:RoutingType>
</t:Mailbox>
<t:ResponseType>{optional_tentative.response}</t:ResponseType>
<t:LastResponseTime>{optional_tentative.last_response:%Y-%m-%dT%H:%M:%SZ}</t:LastResponseTime>
</t:Attendee>
<t:Attendee>
<t:Mailbox>
<t:Name>{optional_declined.name}</t:Name>
<t:EmailAddress>{optional_declined.email}</t:EmailAddress>
<t:RoutingType>SMTP</t:RoutingType>
</t:Mailbox>
<t:ResponseType>{optional_declined.response}</t:ResponseType>
<t:LastResponseTime>{optional_declined.last_response:%Y-%m-%dT%H:%M:%SZ}</t:LastResponseTime>
</t:Attendee>
<t:Attendee>
<t:Mailbox>
<t:Name>{optional_unknown.name}</t:Name>
<t:EmailAddress>{optional_unknown.email}</t:EmailAddress>
<t:RoutingType>SMTP</t:RoutingType>
</t:Mailbox>
<t:ResponseType>{optional_unknown.response}</t:ResponseType>
</t:Attendee>
</t:OptionalAttendees>
<t:Resources>
<t:Attendee>
<t:Mailbox>
<t:Name>{resource.name}</t:Name>
<t:EmailAddress>{resource.email}</t:EmailAddress>
<t:RoutingType>SMTP</t:RoutingType>
</t:Mailbox>
<t:ResponseType>{resource.response}</t:ResponseType>
<t:LastResponseTime>{resource.last_response:%Y-%m-%dT%H:%M:%SZ}</t:LastResponseTime>
</t:Attendee>
</t:Resources>
<t:ConflictingMeetingCount>1</t:ConflictingMeetingCount>
<t:AdjacentMeetingCount>1</t:AdjacentMeetingCount>
<t:ConflictingMeetings>
<t:CalendarItem>
<t:ItemId Id="{conflict_event.id}" ChangeKey="{conflict_event.change_key}"/>
<t:Subject>{conflict_event.subject}</t:Subject>
<t:Start>{conflict_event.start:%Y-%m-%dT%H:%M:%SZ}</t:Start>
<t:End>{conflict_event.end:%Y-%m-%dT%H:%M:%SZ}</t:End>
<t:LegacyFreeBusyStatus>Busy</t:LegacyFreeBusyStatus>
<t:Location>{conflict_event.location}</t:Location>
</t:CalendarItem>
</t:ConflictingMeetings>
<t:AdjacentMeetings>
<t:CalendarItem>
<t:ItemId Id="dinosaur" ChangeKey="goesrarrr"/>
<t:Subject>my other OTHER awesome event</t:Subject>
<t:Start>{event.start:%Y-%m-%dT%H:%M:%SZ}</t:Start>
<t:End>{event.end:%Y-%m-%dT%H:%M:%SZ}</t:End>
<t:LegacyFreeBusyStatus>Busy</t:LegacyFreeBusyStatus>
<t:Location>Outside</t:Location>
</t:CalendarItem>
</t:AdjacentMeetings>
<t:Duration>PT1H</t:Duration>
<t:TimeZone>(UTC-08:00) Pacific Time (US &amp; Canada)</t:TimeZone>
<t:AppointmentSequenceNumber>0</t:AppointmentSequenceNumber>
<t:AppointmentState>1</t:AppointmentState>
</t:CalendarItem>
</m:Items>
</m:GetItemResponseMessage>
</m:ResponseMessages>
</m:GetItemResponse>
</s:Body>
</s:Envelope>
""".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"""<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<h:ServerVersionInfo xmlns:h="http://schemas.microsoft.com/exchange/services/2006/types" xmlns="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" MajorVersion="14" MinorVersion="2" MajorBuildNumber="328" MinorBuildNumber="11"/>
Expand Down
13 changes: 0 additions & 13 deletions tests/exchange2010/test_get_attachment.py
Expand Up @@ -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 = {
Expand Down
65 changes: 65 additions & 0 deletions 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])

0 comments on commit 00eedf6

Please sign in to comment.