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