thirdparty/google_appengine/google/appengine/api/datastore_entities.py
changeset 109 620f9b141567
equal deleted inserted replaced
108:261778de26ff 109:620f9b141567
       
     1 #!/usr/bin/env python
       
     2 #
       
     3 # Copyright 2007 Google Inc.
       
     4 #
       
     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
       
     8 #
       
     9 #     http://www.apache.org/licenses/LICENSE-2.0
       
    10 #
       
    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.
       
    16 #
       
    17 
       
    18 """Classes for common kinds, including Contact, Message, and Event.
       
    19 
       
    20 Most of these kinds are based on the gd namespace "kinds" from GData:
       
    21 
       
    22   http://code.google.com/apis/gdata/common-elements.html
       
    23 """
       
    24 
       
    25 
       
    26 
       
    27 
       
    28 
       
    29 import types
       
    30 import urlparse
       
    31 from xml.sax import saxutils
       
    32 from google.appengine.datastore import datastore_pb
       
    33 from google.appengine.api import datastore
       
    34 from google.appengine.api import datastore_errors
       
    35 from google.appengine.api import datastore_types
       
    36 
       
    37 class GdKind(datastore.Entity):
       
    38   """ A base class for gd namespace kinds.
       
    39 
       
    40   This class contains common logic for all gd namespace kinds. For example,
       
    41   this class translates datastore (app id, kind, key) tuples to tag:
       
    42   URIs appropriate for use in <key> tags.
       
    43   """
       
    44 
       
    45   HEADER = u"""<entry xmlns:gd='http://schemas.google.com/g/2005'>
       
    46   <category scheme='http://schemas.google.com/g/2005#kind'
       
    47             term='http://schemas.google.com/g/2005#%s' />"""
       
    48   FOOTER = u"""
       
    49 </entry>"""
       
    50 
       
    51   _kind_properties = set()
       
    52   _contact_properties = set()
       
    53 
       
    54   def __init__(self, kind, title, kind_properties, contact_properties=[]):
       
    55     """ Ctor.
       
    56 
       
    57     title is the name of this particular entity, e.g. Bob Jones or Mom's
       
    58     Birthday Party.
       
    59 
       
    60     kind_properties is a list of property names that should be included in
       
    61     this entity's XML encoding as first-class XML elements, instead of
       
    62     <property> elements. 'title' and 'content' are added to kind_properties
       
    63     automatically, and may not appear in contact_properties.
       
    64 
       
    65     contact_properties is a list of property names that are Keys that point to
       
    66     Contact entities, and should be included in this entity's XML encoding as
       
    67     <gd:who> elements. If a property name is included in both kind_properties
       
    68     and contact_properties, it is treated as a Contact property.
       
    69 
       
    70     Args:
       
    71     kind: string
       
    72     title: string
       
    73     kind_properties: list of strings
       
    74     contact_properties: list of string
       
    75     """
       
    76     datastore.Entity.__init__(self, kind)
       
    77 
       
    78     if not isinstance(title, types.StringTypes):
       
    79       raise datastore_errors.BadValueError(
       
    80         'Expected a string for title; received %s (a %s).' %
       
    81         (title, datastore_types.typename(title)))
       
    82     self['title'] = title
       
    83     self['content'] = ''
       
    84 
       
    85     self._contact_properties = set(contact_properties)
       
    86     assert not self._contact_properties.intersection(self.keys())
       
    87 
       
    88     self._kind_properties = set(kind_properties) - self._contact_properties
       
    89     self._kind_properties.add('title')
       
    90     self._kind_properties.add('content')
       
    91 
       
    92   def _KindPropertiesToXml(self):
       
    93     """ Convert the properties that are part of this gd kind to XML. For
       
    94     testability, the XML elements in the output are sorted alphabetically
       
    95     by property name.
       
    96 
       
    97     Returns:
       
    98     string  # the XML representation of the gd kind properties
       
    99     """
       
   100     properties = self._kind_properties.intersection(set(self.keys()))
       
   101 
       
   102     xml = u''
       
   103     for prop in sorted(properties):
       
   104       prop_xml = saxutils.quoteattr(prop)[1:-1]
       
   105 
       
   106       value = self[prop]
       
   107       has_toxml = (hasattr(value, 'ToXml') or
       
   108                    isinstance(value, list) and hasattr(value[0], 'ToXml'))
       
   109 
       
   110       for val in self._XmlEscapeValues(prop):
       
   111         if has_toxml:
       
   112           xml += '\n  %s' % val
       
   113         else:
       
   114           xml += '\n  <%s>%s</%s>' % (prop_xml, val, prop_xml)
       
   115 
       
   116     return xml
       
   117 
       
   118 
       
   119   def _ContactPropertiesToXml(self):
       
   120     """ Convert this kind's Contact properties kind to XML. For testability,
       
   121     the XML elements in the output are sorted alphabetically by property name.
       
   122 
       
   123     Returns:
       
   124     string  # the XML representation of the Contact properties
       
   125     """
       
   126     properties = self._contact_properties.intersection(set(self.keys()))
       
   127 
       
   128     xml = u''
       
   129     for prop in sorted(properties):
       
   130       values = self[prop]
       
   131       if not isinstance(values, list):
       
   132         values = [values]
       
   133 
       
   134       for value in values:
       
   135         assert isinstance(value, datastore_types.Key)
       
   136         xml += """
       
   137   <gd:who rel="http://schemas.google.com/g/2005#%s.%s>
       
   138     <gd:entryLink href="%s" />
       
   139   </gd:who>""" % (self.kind().lower(), prop, value.ToTagUri())
       
   140 
       
   141     return xml
       
   142 
       
   143 
       
   144   def _LeftoverPropertiesToXml(self):
       
   145     """ Convert all of this entity's properties that *aren't* part of this gd
       
   146     kind to XML.
       
   147 
       
   148     Returns:
       
   149     string  # the XML representation of the leftover properties
       
   150     """
       
   151     leftovers = set(self.keys())
       
   152     leftovers -= self._kind_properties
       
   153     leftovers -= self._contact_properties
       
   154     if leftovers:
       
   155       return u'\n  ' + '\n  '.join(self._PropertiesToXml(leftovers))
       
   156     else:
       
   157       return u''
       
   158 
       
   159   def ToXml(self):
       
   160     """ Returns an XML representation of this entity, as a string.
       
   161     """
       
   162     xml = GdKind.HEADER % self.kind().lower()
       
   163     xml += self._KindPropertiesToXml()
       
   164     xml += self._ContactPropertiesToXml()
       
   165     xml += self._LeftoverPropertiesToXml()
       
   166     xml += GdKind.FOOTER
       
   167     return xml
       
   168 
       
   169 
       
   170 class Message(GdKind):
       
   171   """A message, such as an email, a discussion group posting, or a comment.
       
   172 
       
   173   Includes the message title, contents, participants, and other properties.
       
   174 
       
   175   This is the gd Message kind. See:
       
   176   http://code.google.com/apis/gdata/common-elements.html#gdMessageKind
       
   177 
       
   178   These properties are meaningful. They are all optional.
       
   179 
       
   180   property name  property type    meaning
       
   181   -------------------------------------
       
   182   title          string         message subject
       
   183   content        string         message body
       
   184   from           Contact*       sender
       
   185   to             Contact*       primary recipient
       
   186   cc             Contact*       CC recipient
       
   187   bcc            Contact*       BCC recipient
       
   188   reply-to       Contact*       intended recipient of replies
       
   189   link           Link*          attachment
       
   190   category       Category*      tag or label associated with this message
       
   191   geoPt          GeoPt*         geographic location the message was posted from
       
   192   rating         Rating*        message rating, as defined by the application
       
   193 
       
   194   * means this property may be repeated.
       
   195 
       
   196   The Contact properties should be Keys of Contact entities. They are
       
   197   represented in the XML encoding as linked <gd:who> elements.
       
   198   """
       
   199   KIND_PROPERTIES = ['title', 'content', 'link', 'category', 'geoPt', 'rating']
       
   200   CONTACT_PROPERTIES = ['from', 'to', 'cc', 'bcc', 'reply-to']
       
   201 
       
   202   def __init__(self, title, kind='Message'):
       
   203     GdKind.__init__(self, kind, title, Message.KIND_PROPERTIES,
       
   204                     Message.CONTACT_PROPERTIES)
       
   205 
       
   206 
       
   207 class Event(GdKind):
       
   208   """A calendar event.
       
   209 
       
   210   Includes the event title, description, location, organizer, start and end
       
   211   time, and other details.
       
   212 
       
   213   This is the gd Event kind. See:
       
   214   http://code.google.com/apis/gdata/common-elements.html#gdEventKind
       
   215 
       
   216   These properties are meaningful. They are all optional.
       
   217 
       
   218   property name  property type    meaning
       
   219   -------------------------------------
       
   220   title          string         event name
       
   221   content        string         event description
       
   222   author         string         the organizer's name
       
   223   where          string*        human-readable location (not a GeoPt)
       
   224   startTime      timestamp      start time
       
   225   endTime        timestamp      end time
       
   226   eventStatus    string         one of the Event.Status values
       
   227   link           Link*          page with more information
       
   228   category       Category*      tag or label associated with this event
       
   229   attendee       Contact*       attendees and other related people
       
   230 
       
   231   * means this property may be repeated.
       
   232 
       
   233   The Contact properties should be Keys of Contact entities. They are
       
   234   represented in the XML encoding as linked <gd:who> elements.
       
   235   """
       
   236   KIND_PROPERTIES = ['title', 'content', 'author', 'where', 'startTime',
       
   237                      'endTime', 'eventStatus', 'link', 'category']
       
   238   CONTACT_PROPERTIES = ['attendee']
       
   239 
       
   240   class Status:
       
   241     CONFIRMED = 'confirmed'
       
   242     TENTATIVE = 'tentative'
       
   243     CANCELED = 'canceled'
       
   244 
       
   245   def __init__(self, title, kind='Event'):
       
   246     GdKind.__init__(self, kind, title, Event.KIND_PROPERTIES,
       
   247                     Event.CONTACT_PROPERTIES)
       
   248 
       
   249   def ToXml(self):
       
   250     """ Override GdKind.ToXml() to special-case author, gd:where, gd:when, and
       
   251     gd:eventStatus.
       
   252     """
       
   253     xml = GdKind.HEADER % self.kind().lower()
       
   254 
       
   255     self._kind_properties = set(Contact.KIND_PROPERTIES)
       
   256     xml += self._KindPropertiesToXml()
       
   257 
       
   258     if 'author' in self:
       
   259       xml += """
       
   260   <author><name>%s</name></author>""" % self['author']
       
   261 
       
   262     if 'eventStatus' in self:
       
   263       xml += """
       
   264   <gd:eventStatus value="http://schemas.google.com/g/2005#event.%s" />""" % (
       
   265     self['eventStatus'])
       
   266 
       
   267     if 'where' in self:
       
   268       lines = ['<gd:where valueString="%s" />' % val
       
   269                for val in self._XmlEscapeValues('where')]
       
   270       xml += '\n  ' + '\n  '.join(lines)
       
   271 
       
   272     iso_format = '%Y-%m-%dT%H:%M:%S'
       
   273     xml += '\n  <gd:when'
       
   274     for key in ['startTime', 'endTime']:
       
   275       if key in self:
       
   276         xml += ' %s="%s"' % (key, self[key].isoformat())
       
   277     xml += ' />'
       
   278 
       
   279     self._kind_properties.update(['author', 'where', 'startTime', 'endTime',
       
   280                                   'eventStatus'])
       
   281     xml += self._ContactPropertiesToXml()
       
   282     xml += self._LeftoverPropertiesToXml()
       
   283     xml += GdKind.FOOTER
       
   284     return xml
       
   285 
       
   286 
       
   287 class Contact(GdKind):
       
   288   """A contact: a person, a venue such as a club or a restaurant, or an
       
   289   organization.
       
   290 
       
   291   This is the gd Contact kind. See:
       
   292   http://code.google.com/apis/gdata/common-elements.html#gdContactKind
       
   293 
       
   294   Most of the information about the contact is in the <gd:contactSection>
       
   295   element; see the reference section for that element for details.
       
   296 
       
   297   These properties are meaningful. They are all optional.
       
   298 
       
   299   property name  property type    meaning
       
   300   -------------------------------------
       
   301   title          string         contact's name
       
   302   content        string         notes
       
   303   email          Email*         email address
       
   304   geoPt          GeoPt*         geographic location
       
   305   im             IM*            IM address
       
   306   phoneNumber    Phonenumber*   phone number
       
   307   postalAddress  PostalAddress* mailing address
       
   308   link           Link*          link to more information
       
   309   category       Category*      tag or label associated with this contact
       
   310 
       
   311   * means this property may be repeated.
       
   312   """
       
   313   CONTACT_SECTION_HEADER = """
       
   314   <gd:contactSection>"""
       
   315   CONTACT_SECTION_FOOTER = """
       
   316   </gd:contactSection>"""
       
   317 
       
   318   KIND_PROPERTIES = ['title', 'content', 'link', 'category']
       
   319 
       
   320   CONTACT_SECTION_PROPERTIES = ['email', 'geoPt', 'im', 'phoneNumber',
       
   321                                 'postalAddress']
       
   322 
       
   323   def __init__(self, title, kind='Contact'):
       
   324     GdKind.__init__(self, kind, title, Contact.KIND_PROPERTIES)
       
   325 
       
   326   def ToXml(self):
       
   327     """ Override GdKind.ToXml() to put some properties inside a
       
   328     gd:contactSection.
       
   329     """
       
   330     xml = GdKind.HEADER % self.kind().lower()
       
   331 
       
   332     self._kind_properties = set(Contact.KIND_PROPERTIES)
       
   333     xml += self._KindPropertiesToXml()
       
   334 
       
   335     xml += Contact.CONTACT_SECTION_HEADER
       
   336     self._kind_properties = set(Contact.CONTACT_SECTION_PROPERTIES)
       
   337     xml += self._KindPropertiesToXml()
       
   338     xml += Contact.CONTACT_SECTION_FOOTER
       
   339 
       
   340     self._kind_properties.update(Contact.KIND_PROPERTIES)
       
   341     xml += self._LeftoverPropertiesToXml()
       
   342     xml += GdKind.FOOTER
       
   343     return xml