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 |
|