1.9.30 sync.
[gae.git] / python / google / appengine / api / datastore_entities.py
blob26fabad8a74db75142f43b687f0be05624331519
1 #!/usr/bin/env python
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
27 """
39 import types
40 import urlparse
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.
53 """
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' />"""
58 FOOTER = u"""
59 </entry>"""
61 _kind_properties = set()
62 _contact_properties = set()
64 def __init__(self, kind, title, kind_properties, contact_properties=[]):
65 """ Ctor.
67 title is the name of this particular entity, e.g. Bob Jones or Mom's
68 Birthday Party.
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.
80 Args:
81 kind: string
82 title: string
83 kind_properties: list of strings
84 contact_properties: list of string
85 """
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)))
92 self['title'] = title
93 self['content'] = ''
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
106 by property name.
108 Returns:
109 string # the XML representation of the gd kind properties
111 properties = self._kind_properties.intersection(set(self.keys()))
113 xml = u''
114 for prop in sorted(properties):
115 prop_xml = saxutils.quoteattr(prop)[1:-1]
117 value = self[prop]
118 has_toxml = (hasattr(value, 'ToXml') or
119 isinstance(value, list) and hasattr(value[0], 'ToXml'))
121 for val in self._XmlEscapeValues(prop):
124 if has_toxml:
125 xml += '\n %s' % val
126 else:
127 xml += '\n <%s>%s</%s>' % (prop_xml, val, prop_xml)
129 return 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.
136 Returns:
137 string # the XML representation of the Contact properties
139 properties = self._contact_properties.intersection(set(self.keys()))
141 xml = u''
142 for prop in sorted(properties):
143 values = self[prop]
144 if not isinstance(values, list):
145 values = [values]
147 for value in values:
148 assert isinstance(value, datastore_types.Key)
149 xml += """
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())
154 return xml
157 def _LeftoverPropertiesToXml(self):
158 """ Convert all of this entity's properties that *aren't* part of this gd
159 kind to XML.
161 Returns:
162 string # the XML representation of the leftover properties
164 leftovers = set(self.keys())
165 leftovers -= self._kind_properties
166 leftovers -= self._contact_properties
167 if leftovers:
168 return u'\n ' + '\n '.join(self._PropertiesToXml(leftovers))
169 else:
170 return u''
172 def ToXml(self):
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()
179 xml += GdKind.FOOTER
180 return xml
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
197 from Contact* sender
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)
220 class Event(GdKind):
221 """A calendar event.
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']
253 class Status:
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)
262 def ToXml(self):
263 """ Override GdKind.ToXml() to special-case author, gd:where, gd:when, and
264 gd:eventStatus.
266 xml = GdKind.HEADER % self.kind().lower()
268 self._kind_properties = set(Contact.KIND_PROPERTIES)
269 xml += self._KindPropertiesToXml()
272 if 'author' in self:
273 xml += """
274 <author><name>%s</name></author>""" % self['author']
277 if 'eventStatus' in self:
278 xml += """
279 <gd:eventStatus value="http://schemas.google.com/g/2005#event.%s" />""" % (
280 self['eventStatus'])
283 if 'where' in self:
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'
290 xml += '\n <gd:when'
291 for key in ['startTime', 'endTime']:
292 if key in self:
293 xml += ' %s="%s"' % (key, self[key].isoformat())
294 xml += ' />'
296 self._kind_properties.update(['author', 'where', 'startTime', 'endTime',
297 'eventStatus'])
298 xml += self._ContactPropertiesToXml()
299 xml += self._LeftoverPropertiesToXml()
300 xml += GdKind.FOOTER
301 return xml
304 class Contact(GdKind):
305 """A contact: a person, a venue such as a club or a restaurant, or an
306 organization.
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
319 content string notes
320 email Email* email address
321 geoPt GeoPt* geographic location
322 im IM* IM address
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',
340 'postalAddress']
342 def __init__(self, title, kind='Contact'):
343 GdKind.__init__(self, kind, title, Contact.KIND_PROPERTIES)
345 def ToXml(self):
346 """ Override GdKind.ToXml() to put some properties inside a
347 gd:contactSection.
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()
363 xml += GdKind.FOOTER
364 return xml