app/polymodel/__init__.py
changeset 571 2aad108bc617
parent 570 cbd07fbdc125
child 572 1b3e7280743a
equal deleted inserted replaced
570:cbd07fbdc125 571:2aad108bc617
     1 #
       
     2 # Copyright (c) 2008 Andreas Blixt <andreas@blixt.org>
       
     3 # Project homepage: <http://code.google.com/p/blixt/>
       
     4 #
       
     5 # License: MIT license <http://www.opensource.org/licenses/mit-license.php>
       
     6 #
       
     7 
       
     8 """Data-related Google App Engine extensions.
       
     9 """
       
    10 
       
    11 from google.appengine.ext import db
       
    12 
       
    13 import sys
       
    14 
       
    15 # Add ModelWithFieldAttributes *before* PolyModel, so that everything does
       
    16 # not become a "ModelWithFieldAttributes" in the Datastore.
       
    17 from soc.models import base
       
    18 
       
    19 class Error(Exception):
       
    20     """Base of all exceptions in the blixt.data module."""
       
    21     pass
       
    22 
       
    23 class PolyModel(base.ModelWithFieldAttributes):
       
    24     """An extension to Google App Engine models that improves the support for
       
    25     inheritance.
       
    26 
       
    27     Any models extending a model that extends PolyModel will be stored in the
       
    28     datastore as the same kind as the model that extends PolyModel, but with
       
    29     their additional properties. When they are retrieved from the datastore,
       
    30     they will be loaded as the appropiate model class.
       
    31 
       
    32     The difference from the Model class is that queries will include models that
       
    33     inherit from the model being queried.
       
    34     """
       
    35     inheritance_line = db.StringListProperty()
       
    36 
       
    37     def __init__(self, parent = None, key_name = None, _app = None, **kwds):
       
    38         """Creates a new instance of this polymorphic model.
       
    39 
       
    40         Args:
       
    41           parent: Parent instance for this instance or None, indicating a top-
       
    42             level instance.
       
    43           key_name: Name for new model instance.
       
    44           _app: Intentionally undocumented.
       
    45           args: Keyword arguments mapping to properties of model.
       
    46         """
       
    47         if self.__class__ == PolyModel or not isinstance(self, PolyModel):
       
    48             raise Error('Instances of PolyModel must be created through a '
       
    49                         'subclass of PolyModel.')
       
    50 
       
    51         line = []
       
    52         for c in self.__class__.__mro__:
       
    53             if c == PolyModel:
       
    54                 self.__class__._kind = p
       
    55                 break
       
    56             line.append('%s.%s' % (c.__module__, c.__name__))
       
    57             p = c
       
    58 
       
    59         kwds['inheritance_line'] = line
       
    60 
       
    61         super(PolyModel, self).__init__(parent = parent, key_name = key_name,
       
    62                                         _app = _app, **kwds)
       
    63 
       
    64     @classmethod
       
    65     def _kind_type(cls):
       
    66         """Gets the class highest in the model inheritance hierarchy (the class
       
    67         that will be used as the datastore kind.)
       
    68         """
       
    69         p = cls
       
    70 
       
    71         for c in cls.__mro__:
       
    72             # The meta-class 'PropertiedClass' calls kind() which leads here.
       
    73             # The variable 'PolyModel' is not assigned until after the meta-
       
    74             # class has finished setting up the class. Therefore, a string
       
    75             # comparison is used instead of a value comparison.
       
    76             if c.__name__ == 'PolyModel': break
       
    77             p = c
       
    78 
       
    79         return p
       
    80 
       
    81     @classmethod
       
    82     def all(cls):
       
    83         """Returns a query over all instances of this model, as well as the
       
    84         instances of any descendant models, from the datastore.
       
    85 
       
    86         Returns:
       
    87           Query that will retrieve all instances from entity collection.
       
    88         """
       
    89         qry = super(PolyModel, cls).all()
       
    90 
       
    91         if cls != cls._kind_type():
       
    92             full_name = '%s.%s' % (cls.__module__, cls.__name__)
       
    93             qry.filter('inheritance_line =', full_name)
       
    94 
       
    95         return qry
       
    96 
       
    97     @classmethod
       
    98     def from_entity(cls, entity):
       
    99         """Converts the entity representation of this model to an instance.
       
   100 
       
   101         Converts datastore.Entity instance to an instance of the appropiate
       
   102         class, which can be cls or any descendant thereof.
       
   103 
       
   104         Args:
       
   105           entity: Entity loaded directly from datastore.
       
   106 
       
   107         Raises:
       
   108           KindError when cls is incorrect model for entity.
       
   109         """
       
   110         if entity.has_key('inheritance_line'):
       
   111             mod_name, cls_name = entity['inheritance_line'][0].rsplit('.', 1)
       
   112             __import__(mod_name)
       
   113             cls = getattr(sys.modules[mod_name], cls_name)
       
   114         return super(PolyModel, cls).from_entity(entity)
       
   115 
       
   116     @classmethod
       
   117     def kind(cls):
       
   118         """Returns the datastore kind we use for this model.
       
   119 
       
   120         This is the name of the class that is highest up in the inheritance
       
   121         hierarchy of this model.
       
   122         """
       
   123         return cls._kind_type().__name__
       
   124