3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
22 """Classes for common kinds, including Contact, Message, and Event.
24 Most of these kinds are based on the gd namespace "kinds" from GData:
26 http://code.google.com/apis/gdata/common-elements.html
41 from xml
.sax
import saxutils
42 from google
.appengine
.datastore
import datastore_pb
43 from google
.appengine
.api
import datastore
44 from google
.appengine
.api
import datastore_errors
45 from google
.appengine
.api
import datastore_types
47 class GdKind(datastore
.Entity
):
48 """ A base class for gd namespace kinds.
50 This class contains common logic for all gd namespace kinds. For example,
51 this class translates datastore (app id, kind, key) tuples to tag:
52 URIs appropriate for use in <key> tags.
55 HEADER
= u
"""<entry xmlns:gd='http://schemas.google.com/g/2005'>
56 <category scheme='http://schemas.google.com/g/2005#kind'
57 term='http://schemas.google.com/g/2005#%s' />"""
61 _kind_properties
= set()
62 _contact_properties
= set()
64 def __init__(self
, kind
, title
, kind_properties
, contact_properties
=[]):
67 title is the name of this particular entity, e.g. Bob Jones or Mom's
70 kind_properties is a list of property names that should be included in
71 this entity's XML encoding as first-class XML elements, instead of
72 <property> elements. 'title' and 'content' are added to kind_properties
73 automatically, and may not appear in contact_properties.
75 contact_properties is a list of property names that are Keys that point to
76 Contact entities, and should be included in this entity's XML encoding as
77 <gd:who> elements. If a property name is included in both kind_properties
78 and contact_properties, it is treated as a Contact property.
83 kind_properties: list of strings
84 contact_properties: list of string
86 datastore
.Entity
.__init
__(self
, kind
)
88 if not isinstance(title
, types
.StringTypes
):
89 raise datastore_errors
.BadValueError(
90 'Expected a string for title; received %s (a %s).' %
91 (title
, datastore_types
.typename(title
)))
96 self
._contact
_properties
= set(contact_properties
)
97 assert not self
._contact
_properties
.intersection(self
.keys())
99 self
._kind
_properties
= set(kind_properties
) - self
._contact
_properties
100 self
._kind
_properties
.add('title')
101 self
._kind
_properties
.add('content')
103 def _KindPropertiesToXml(self
):
104 """ Convert the properties that are part of this gd kind to XML. For
105 testability, the XML elements in the output are sorted alphabetically
109 string # the XML representation of the gd kind properties
111 properties
= self
._kind
_properties
.intersection(set(self
.keys()))
114 for prop
in sorted(properties
):
115 prop_xml
= saxutils
.quoteattr(prop
)[1:-1]
118 has_toxml
= (hasattr(value
, 'ToXml') or
119 isinstance(value
, list) and hasattr(value
[0], 'ToXml'))
121 for val
in self
._XmlEscapeValues
(prop
):
127 xml
+= '\n <%s>%s</%s>' % (prop_xml
, val
, prop_xml
)
132 def _ContactPropertiesToXml(self
):
133 """ Convert this kind's Contact properties kind to XML. For testability,
134 the XML elements in the output are sorted alphabetically by property name.
137 string # the XML representation of the Contact properties
139 properties
= self
._contact
_properties
.intersection(set(self
.keys()))
142 for prop
in sorted(properties
):
144 if not isinstance(values
, list):
148 assert isinstance(value
, datastore_types
.Key
)
150 <gd:who rel="http://schemas.google.com/g/2005#%s.%s>
151 <gd:entryLink href="%s" />
152 </gd:who>""" % (self
.kind().lower(), prop
, value
.ToTagUri())
157 def _LeftoverPropertiesToXml(self
):
158 """ Convert all of this entity's properties that *aren't* part of this gd
162 string # the XML representation of the leftover properties
164 leftovers
= set(self
.keys())
165 leftovers
-= self
._kind
_properties
166 leftovers
-= self
._contact
_properties
168 return u
'\n ' + '\n '.join(self
._PropertiesToXml
(leftovers
))
173 """ Returns an XML representation of this entity, as a string.
175 xml
= GdKind
.HEADER
% self
.kind().lower()
176 xml
+= self
._KindPropertiesToXml
()
177 xml
+= self
._ContactPropertiesToXml
()
178 xml
+= self
._LeftoverPropertiesToXml
()
183 class Message(GdKind
):
184 """A message, such as an email, a discussion group posting, or a comment.
186 Includes the message title, contents, participants, and other properties.
188 This is the gd Message kind. See:
189 http://code.google.com/apis/gdata/common-elements.html#gdMessageKind
191 These properties are meaningful. They are all optional.
193 property name property type meaning
194 -------------------------------------
195 title string message subject
196 content string message body
198 to Contact* primary recipient
199 cc Contact* CC recipient
200 bcc Contact* BCC recipient
201 reply-to Contact* intended recipient of replies
202 link Link* attachment
203 category Category* tag or label associated with this message
204 geoPt GeoPt* geographic location the message was posted from
205 rating Rating* message rating, as defined by the application
207 * means this property may be repeated.
209 The Contact properties should be Keys of Contact entities. They are
210 represented in the XML encoding as linked <gd:who> elements.
212 KIND_PROPERTIES
= ['title', 'content', 'link', 'category', 'geoPt', 'rating']
213 CONTACT_PROPERTIES
= ['from', 'to', 'cc', 'bcc', 'reply-to']
215 def __init__(self
, title
, kind
='Message'):
216 GdKind
.__init
__(self
, kind
, title
, Message
.KIND_PROPERTIES
,
217 Message
.CONTACT_PROPERTIES
)
223 Includes the event title, description, location, organizer, start and end
224 time, and other details.
226 This is the gd Event kind. See:
227 http://code.google.com/apis/gdata/common-elements.html#gdEventKind
229 These properties are meaningful. They are all optional.
231 property name property type meaning
232 -------------------------------------
233 title string event name
234 content string event description
235 author string the organizer's name
236 where string* human-readable location (not a GeoPt)
237 startTime timestamp start time
238 endTime timestamp end time
239 eventStatus string one of the Event.Status values
240 link Link* page with more information
241 category Category* tag or label associated with this event
242 attendee Contact* attendees and other related people
244 * means this property may be repeated.
246 The Contact properties should be Keys of Contact entities. They are
247 represented in the XML encoding as linked <gd:who> elements.
249 KIND_PROPERTIES
= ['title', 'content', 'author', 'where', 'startTime',
250 'endTime', 'eventStatus', 'link', 'category']
251 CONTACT_PROPERTIES
= ['attendee']
254 CONFIRMED
= 'confirmed'
255 TENTATIVE
= 'tentative'
256 CANCELED
= 'canceled'
258 def __init__(self
, title
, kind
='Event'):
259 GdKind
.__init
__(self
, kind
, title
, Event
.KIND_PROPERTIES
,
260 Event
.CONTACT_PROPERTIES
)
263 """ Override GdKind.ToXml() to special-case author, gd:where, gd:when, and
266 xml
= GdKind
.HEADER
% self
.kind().lower()
268 self
._kind
_properties
= set(Contact
.KIND_PROPERTIES
)
269 xml
+= self
._KindPropertiesToXml
()
274 <author><name>%s</name></author>""" % self
['author']
277 if 'eventStatus' in self
:
279 <gd:eventStatus value="http://schemas.google.com/g/2005#event.%s" />""" % (
284 lines
= ['<gd:where valueString="%s" />' % val
285 for val
in self
._XmlEscapeValues
('where')]
286 xml
+= '\n ' + '\n '.join(lines
)
289 iso_format
= '%Y-%m-%dT%H:%M:%S'
291 for key
in ['startTime', 'endTime']:
293 xml
+= ' %s="%s"' % (key
, self
[key
].isoformat())
296 self
._kind
_properties
.update(['author', 'where', 'startTime', 'endTime',
298 xml
+= self
._ContactPropertiesToXml
()
299 xml
+= self
._LeftoverPropertiesToXml
()
304 class Contact(GdKind
):
305 """A contact: a person, a venue such as a club or a restaurant, or an
308 This is the gd Contact kind. See:
309 http://code.google.com/apis/gdata/common-elements.html#gdContactKind
311 Most of the information about the contact is in the <gd:contactSection>
312 element; see the reference section for that element for details.
314 These properties are meaningful. They are all optional.
316 property name property type meaning
317 -------------------------------------
318 title string contact's name
320 email Email* email address
321 geoPt GeoPt* geographic location
323 phoneNumber Phonenumber* phone number
324 postalAddress PostalAddress* mailing address
325 link Link* link to more information
326 category Category* tag or label associated with this contact
328 * means this property may be repeated.
330 CONTACT_SECTION_HEADER
= """
331 <gd:contactSection>"""
332 CONTACT_SECTION_FOOTER
= """
333 </gd:contactSection>"""
336 KIND_PROPERTIES
= ['title', 'content', 'link', 'category']
339 CONTACT_SECTION_PROPERTIES
= ['email', 'geoPt', 'im', 'phoneNumber',
342 def __init__(self
, title
, kind
='Contact'):
343 GdKind
.__init
__(self
, kind
, title
, Contact
.KIND_PROPERTIES
)
346 """ Override GdKind.ToXml() to put some properties inside a
349 xml
= GdKind
.HEADER
% self
.kind().lower()
352 self
._kind
_properties
= set(Contact
.KIND_PROPERTIES
)
353 xml
+= self
._KindPropertiesToXml
()
356 xml
+= Contact
.CONTACT_SECTION_HEADER
357 self
._kind
_properties
= set(Contact
.CONTACT_SECTION_PROPERTIES
)
358 xml
+= self
._KindPropertiesToXml
()
359 xml
+= Contact
.CONTACT_SECTION_FOOTER
361 self
._kind
_properties
.update(Contact
.KIND_PROPERTIES
)
362 xml
+= self
._LeftoverPropertiesToXml
()