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
40 from xml
.sax
import saxutils
41 from google
.appengine
.datastore
import datastore_pb
42 from google
.appengine
.api
import datastore
43 from google
.appengine
.api
import datastore_errors
44 from google
.appengine
.api
import datastore_types
46 class GdKind(datastore
.Entity
):
47 """ A base class for gd namespace kinds.
49 This class contains common logic for all gd namespace kinds. For example,
50 this class translates datastore (app id, kind, key) tuples to tag:
51 URIs appropriate for use in <key> tags.
54 HEADER
= u
"""<entry xmlns:gd='http://schemas.google.com/g/2005'>
55 <category scheme='http://schemas.google.com/g/2005#kind'
56 term='http://schemas.google.com/g/2005#%s' />"""
60 _kind_properties
= set()
61 _contact_properties
= set()
63 def __init__(self
, kind
, title
, kind_properties
, contact_properties
=[]):
66 title is the name of this particular entity, e.g. Bob Jones or Mom's
69 kind_properties is a list of property names that should be included in
70 this entity's XML encoding as first-class XML elements, instead of
71 <property> elements. 'title' and 'content' are added to kind_properties
72 automatically, and may not appear in contact_properties.
74 contact_properties is a list of property names that are Keys that point to
75 Contact entities, and should be included in this entity's XML encoding as
76 <gd:who> elements. If a property name is included in both kind_properties
77 and contact_properties, it is treated as a Contact property.
82 kind_properties: list of strings
83 contact_properties: list of string
85 datastore
.Entity
.__init
__(self
, kind
)
87 if not isinstance(title
, types
.StringTypes
):
88 raise datastore_errors
.BadValueError(
89 'Expected a string for title; received %s (a %s).' %
90 (title
, datastore_types
.typename(title
)))
95 self
._contact
_properties
= set(contact_properties
)
96 assert not self
._contact
_properties
.intersection(self
.keys())
98 self
._kind
_properties
= set(kind_properties
) - self
._contact
_properties
99 self
._kind
_properties
.add('title')
100 self
._kind
_properties
.add('content')
102 def _KindPropertiesToXml(self
):
103 """ Convert the properties that are part of this gd kind to XML. For
104 testability, the XML elements in the output are sorted alphabetically
108 string # the XML representation of the gd kind properties
110 properties
= self
._kind
_properties
.intersection(set(self
.keys()))
113 for prop
in sorted(properties
):
114 prop_xml
= saxutils
.quoteattr(prop
)[1:-1]
117 has_toxml
= (hasattr(value
, 'ToXml') or
118 isinstance(value
, list) and hasattr(value
[0], 'ToXml'))
120 for val
in self
._XmlEscapeValues
(prop
):
126 xml
+= '\n <%s>%s</%s>' % (prop_xml
, val
, prop_xml
)
131 def _ContactPropertiesToXml(self
):
132 """ Convert this kind's Contact properties kind to XML. For testability,
133 the XML elements in the output are sorted alphabetically by property name.
136 string # the XML representation of the Contact properties
138 properties
= self
._contact
_properties
.intersection(set(self
.keys()))
141 for prop
in sorted(properties
):
143 if not isinstance(values
, list):
147 assert isinstance(value
, datastore_types
.Key
)
149 <gd:who rel="http://schemas.google.com/g/2005#%s.%s>
150 <gd:entryLink href="%s" />
151 </gd:who>""" % (self
.kind().lower(), prop
, value
.ToTagUri())
156 def _LeftoverPropertiesToXml(self
):
157 """ Convert all of this entity's properties that *aren't* part of this gd
161 string # the XML representation of the leftover properties
163 leftovers
= set(self
.keys())
164 leftovers
-= self
._kind
_properties
165 leftovers
-= self
._contact
_properties
167 return u
'\n ' + '\n '.join(self
._PropertiesToXml
(leftovers
))
172 """ Returns an XML representation of this entity, as a string.
174 xml
= GdKind
.HEADER
% self
.kind().lower()
175 xml
+= self
._KindPropertiesToXml
()
176 xml
+= self
._ContactPropertiesToXml
()
177 xml
+= self
._LeftoverPropertiesToXml
()
182 class Message(GdKind
):
183 """A message, such as an email, a discussion group posting, or a comment.
185 Includes the message title, contents, participants, and other properties.
187 This is the gd Message kind. See:
188 http://code.google.com/apis/gdata/common-elements.html#gdMessageKind
190 These properties are meaningful. They are all optional.
192 property name property type meaning
193 -------------------------------------
194 title string message subject
195 content string message body
197 to Contact* primary recipient
198 cc Contact* CC recipient
199 bcc Contact* BCC recipient
200 reply-to Contact* intended recipient of replies
201 link Link* attachment
202 category Category* tag or label associated with this message
203 geoPt GeoPt* geographic location the message was posted from
204 rating Rating* message rating, as defined by the application
206 * means this property may be repeated.
208 The Contact properties should be Keys of Contact entities. They are
209 represented in the XML encoding as linked <gd:who> elements.
211 KIND_PROPERTIES
= ['title', 'content', 'link', 'category', 'geoPt', 'rating']
212 CONTACT_PROPERTIES
= ['from', 'to', 'cc', 'bcc', 'reply-to']
214 def __init__(self
, title
, kind
='Message'):
215 GdKind
.__init
__(self
, kind
, title
, Message
.KIND_PROPERTIES
,
216 Message
.CONTACT_PROPERTIES
)
222 Includes the event title, description, location, organizer, start and end
223 time, and other details.
225 This is the gd Event kind. See:
226 http://code.google.com/apis/gdata/common-elements.html#gdEventKind
228 These properties are meaningful. They are all optional.
230 property name property type meaning
231 -------------------------------------
232 title string event name
233 content string event description
234 author string the organizer's name
235 where string* human-readable location (not a GeoPt)
236 startTime timestamp start time
237 endTime timestamp end time
238 eventStatus string one of the Event.Status values
239 link Link* page with more information
240 category Category* tag or label associated with this event
241 attendee Contact* attendees and other related people
243 * means this property may be repeated.
245 The Contact properties should be Keys of Contact entities. They are
246 represented in the XML encoding as linked <gd:who> elements.
248 KIND_PROPERTIES
= ['title', 'content', 'author', 'where', 'startTime',
249 'endTime', 'eventStatus', 'link', 'category']
250 CONTACT_PROPERTIES
= ['attendee']
253 CONFIRMED
= 'confirmed'
254 TENTATIVE
= 'tentative'
255 CANCELED
= 'canceled'
257 def __init__(self
, title
, kind
='Event'):
258 GdKind
.__init
__(self
, kind
, title
, Event
.KIND_PROPERTIES
,
259 Event
.CONTACT_PROPERTIES
)
262 """ Override GdKind.ToXml() to special-case author, gd:where, gd:when, and
265 xml
= GdKind
.HEADER
% self
.kind().lower()
267 self
._kind
_properties
= set(Contact
.KIND_PROPERTIES
)
268 xml
+= self
._KindPropertiesToXml
()
273 <author><name>%s</name></author>""" % self
['author']
276 if 'eventStatus' in self
:
278 <gd:eventStatus value="http://schemas.google.com/g/2005#event.%s" />""" % (
283 lines
= ['<gd:where valueString="%s" />' % val
284 for val
in self
._XmlEscapeValues
('where')]
285 xml
+= '\n ' + '\n '.join(lines
)
288 iso_format
= '%Y-%m-%dT%H:%M:%S'
290 for key
in ['startTime', 'endTime']:
292 xml
+= ' %s="%s"' % (key
, self
[key
].isoformat())
295 self
._kind
_properties
.update(['author', 'where', 'startTime', 'endTime',
297 xml
+= self
._ContactPropertiesToXml
()
298 xml
+= self
._LeftoverPropertiesToXml
()
303 class Contact(GdKind
):
304 """A contact: a person, a venue such as a club or a restaurant, or an
307 This is the gd Contact kind. See:
308 http://code.google.com/apis/gdata/common-elements.html#gdContactKind
310 Most of the information about the contact is in the <gd:contactSection>
311 element; see the reference section for that element for details.
313 These properties are meaningful. They are all optional.
315 property name property type meaning
316 -------------------------------------
317 title string contact's name
319 email Email* email address
320 geoPt GeoPt* geographic location
322 phoneNumber Phonenumber* phone number
323 postalAddress PostalAddress* mailing address
324 link Link* link to more information
325 category Category* tag or label associated with this contact
327 * means this property may be repeated.
329 CONTACT_SECTION_HEADER
= """
330 <gd:contactSection>"""
331 CONTACT_SECTION_FOOTER
= """
332 </gd:contactSection>"""
335 KIND_PROPERTIES
= ['title', 'content', 'link', 'category']
338 CONTACT_SECTION_PROPERTIES
= ['email', 'geoPt', 'im', 'phoneNumber',
341 def __init__(self
, title
, kind
='Contact'):
342 GdKind
.__init
__(self
, kind
, title
, Contact
.KIND_PROPERTIES
)
345 """ Override GdKind.ToXml() to put some properties inside a
348 xml
= GdKind
.HEADER
% self
.kind().lower()
351 self
._kind
_properties
= set(Contact
.KIND_PROPERTIES
)
352 xml
+= self
._KindPropertiesToXml
()
355 xml
+= Contact
.CONTACT_SECTION_HEADER
356 self
._kind
_properties
= set(Contact
.CONTACT_SECTION_PROPERTIES
)
357 xml
+= self
._KindPropertiesToXml
()
358 xml
+= Contact
.CONTACT_SECTION_FOOTER
360 self
._kind
_properties
.update(Contact
.KIND_PROPERTIES
)
361 xml
+= self
._LeftoverPropertiesToXml
()