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