Fixes an issue where the organization home page would throw a 505 when no projects...
[Melange.git] / app / reflistprop / __init__.py
blob5b47db79c52714a4e40b6ca0b05631bc72020b05
1 #!/usr/bin/python2.5
3 # Copyright 2007 Ken Tidwell
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.
17 """Simple property for storing ordered lists of Model objects.
19 It should be noted that larger lists are going to be VERY inefficient
20 to load (one get per object).
22 Currently I have no idea where that upper bound might lie, though.
24 A quick usage example:
25 class Bit(db.Model):
26 name = db.StringProperty(required=True)
27 class Holder(db.Model):
28 bits = reflistprop.ReferenceListProperty(Bit, default=None)
29 b1 = Bit(name="first")
30 b1.put() # need to put it so that it is a valid reference object
31 h1 = holder()
32 h1.bits.append(b1)
33 h1.put()
35 These also work:
36 h1.bits = [b1]
38 This throws a db.BadValueError because a string is not an instance of
39 Bit:
40 h1.bits = ["nasty evil string"]
42 This is not good but gets no complaint at assignment time (same behaviour
43 as ListProperty) but we will throw a db.BadValueError if you try to put it
44 into the datastore. (Maybe ListProperty wants to do the same? Or should I be
45 waiting for the datastore internal entity construction to notice the problem
46 and throw?):
47 h1.bits.append("nasty evil string")
49 Yes, of course you can query them. The important bit to understand is
50 that the list is stored as a list of keys in the datastore. So you use
51 the key of the entity in question in your query. (Seems like it would be
52 nice if the property could get involved and do that coercion for you but
53 I don't think it can right now...).
55 Here's a little example:
56 class Thang(db.Model):
57 name = db.StringProperty(required=True)
58 class Holder(db.Model):
59 thangs = langutils.ReferenceListProperty(Thang, default=None)
60 holder1 = Holder()
61 holder1.put()
62 foo = Thang(name="foo")
63 foo.put()
64 holder1.thangs.append(foo)
65 holder1.put()
66 hq = db.GqlQuery("SELECT * FROM Holder where thangs = :1", foo.key())
67 holders = hq.fetch(10)
68 print "Holders =", holders
70 Obtained from:
71 http://groups.google.com/group/google-appengine/msg/d203cc1b93ee22d7
72 """
75 from google.appengine.ext import db
78 class ReferenceListProperty(db.Property):
79 """A property that stores a list of models.
81 This is a parameterized property; the parameter must be a valid
82 Model type, and all items must conform to this type.
83 """
84 def __init__(self, item_type, verbose_name=None, default=None, **kwds):
85 """Construct ReferenceListProperty.
87 Args:
88 item_type: Type for the list items; must be a subclass of Model.
89 verbose_name: Optional verbose name.
90 default: Optional default value; if omitted, an empty list is used.
91 **kwds: Optional additional keyword arguments, passed to base class.
92 """
93 if not issubclass(item_type, db.Model):
94 raise TypeError('Item type should be a subclass of Model')
95 if default is None:
96 default = []
97 self.item_type = item_type
98 super(ReferenceListProperty, self).__init__(verbose_name,
99 default=default,
100 **kwds)
102 def validate(self, value):
103 """Validate list.
105 Note that validation here is just as broken as for ListProperty.
106 The values in the list are only validated if the entire list is
107 swapped out. If the list is directly modified, there is no attempt
108 to validate the new items.
110 Returns:
111 A valid value.
113 Raises:
114 BadValueError if property is not a list whose items are
115 instances of the item_type given to the constructor.
117 value = super(ReferenceListProperty, self).validate(value)
118 if value is not None:
119 if not isinstance(value, list):
120 raise db.BadValueError('Property %s must be a list' %
121 self.name)
122 for item in value:
123 if not isinstance(item, self.item_type):
124 raise db.BadValueError(
125 'Items in the %s list must all be %s instances' %
126 (self.name, self.item_type.__name__))
127 return value
129 def empty(self, value):
130 """Is list property empty.
132 [] is not an empty value.
134 Returns:
135 True if value is None, else False.
136 """
137 return value is None
139 data_type = list
141 def default_value(self):
142 """Default value for list.
144 Because the property supplied to 'default' is a static value,
145 that value must be shallow copied to prevent all fields with
146 default values from sharing the same instance.
148 Returns:
149 Copy of the default value.
150 """
151 return list(super(ReferenceListProperty, self).default_value())
153 def get_value_for_datastore(self, model_instance):
154 """A list of key values is stored.
156 Prior to storage, we validate the items in the list.
157 This check seems to be missing from ListProperty.
159 Args:
160 model_instance: Instance to fetch datastore value from.
162 Returns:
163 A list of the keys for all Models in the value list.
165 value = self.__get__(model_instance, model_instance.__class__)
166 self.validate(value)
167 if value is None:
168 return None
169 else:
170 return [v.key() for v in value]
172 def make_value_from_datastore(self, value):
173 """Recreates the list of Models from the list of keys.
175 Args:
176 value: value retrieved from the datastore entity.
178 Returns:
179 None or a list of Models.
180 """
181 if value is None:
182 return None
183 else:
184 return [db.get(v) for v in value]