Package orm2 :: Module relationships
[hide private]
[frames] | no frames]

Source Code for Module orm2.relationships

  1  #!/usr/bin/env python 
  2  # -*- coding: iso-8859-1 -*- 
  3   
  4  ##  This file is part of orm, The Object Relational Membrane Version 2. 
  5  ## 
  6  ##  Copyright 2002-2006 by Diedrich Vorberg <diedrich@tux4web.de> 
  7  ## 
  8  ##  All Rights Reserved 
  9  ## 
 10  ##  For more Information on orm see the README file. 
 11  ## 
 12  ##  This program is free software; you can redistribute it and/or modify 
 13  ##  it under the terms of the GNU General Public License as published by 
 14  ##  the Free Software Foundation; either version 2 of the License, or 
 15  ##  (at your option) any later version. 
 16  ## 
 17  ##  This program is distributed in the hope that it will be useful, 
 18  ##  but WITHOUT ANY WARRANTY; without even the implied warranty of 
 19  ##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 20  ##  GNU General Public License for more details. 
 21  ## 
 22  ##  You should have received a copy of the GNU General Public License 
 23  ##  along with this program; if not, write to the Free Software 
 24  ##  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
 25  ## 
 26  ##  I have added a copy of the GPL in the file gpl.txt. 
 27   
 28   
 29  # Changelog 
 30  # --------- 
 31  #  
 32  # $Log: relationships.py,v $ 
 33  # Revision 1.15  2006/09/19 18:05:21  diedrich 
 34  # Many2one relationships may be set to None now. 
 35  # 
 36  # Revision 1.14  2006/07/10 22:54:59  diedrich 
 37  # Fixed typo 
 38  # 
 39  # Revision 1.13  2006/07/10 14:23:53  diedrich 
 40  # Allow access to a dbclass.__result__'s views through 
 41  # relationship.result class By adding __getattr__ to 
 42  # relationship.result. 
 43  # 
 44  # Revision 1.12  2006/07/08 17:08:42  diedrich 
 45  # Added all() to many2many relationship 
 46  # 
 47  # Revision 1.11  2006/06/10 18:09:09  diedrich 
 48  # *** empty log message *** 
 49  # 
 50  # Revision 1.10  2006/06/09 09:05:39  diedrich 
 51  # Actually use len() and __len__() as they were intended to be used 
 52  # 
 53  # Revision 1.9  2006/05/13 17:23:41  diedrich 
 54  # Massive docstring update. 
 55  # 
 56  # Revision 1.8  2006/05/02 13:30:45  diedrich 
 57  # Make sure __len__() returns integers and that many2one.child_key is 
 58  # set to the child_class' primary key by default. 
 59  # 
 60  # Revision 1.7  2006/04/28 09:49:27  diedrich 
 61  # Docstring updates for epydoc 
 62  # 
 63  # Revision 1.6  2006/04/28 08:44:19  diedrich 
 64  # Besided some minor changes everywhere, I added the many2one relationship 
 65  # 
 66  # Revision 1.5  2006/04/15 23:20:51  diedrich 
 67  # Wrote key functionality of many2many on the way to my Mom's place... 
 68  # 
 69  # Revision 1.4  2006/02/25 18:09:08  diedrich 
 70  # Fixed small bug 
 71  # 
 72  # Revision 1.3  2006/02/25 17:59:55  diedrich 
 73  # Made the many2one work with multi column keys. 
 74  # 
 75  # Revision 1.2  2006/02/25 00:20:20  diedrich 
 76  # - Added and tested the ability to use multiple column primary keys. 
 77  # - Some small misc bugs. 
 78  # 
 79  # Revision 1.1  2006/01/01 20:37:28  diedrich 
 80  # Initial comit (half way into many2many) 
 81  # 
 82  # 
 83  # 
 84   
 85  """ 
 86  Defines a number of classes based on L{relationship} which model how 
 87  dbclasses interrelate.  
 88  """ 
 89   
 90  import sys 
 91  from types import * 
 92   
 93  from orm2.datatypes import datatype 
 94  from orm2.exceptions import * 
 95  from orm2 import sql, keys 
 96  from orm2.ui import view 
 97   
98 -class relationship(datatype):
99 """ 100 Base class for all relationships. 101 102 For documentation of the other parameters refer to the constructor 103 of L{datatype.datatype}. 104 """
105 - def __init__(self, child_class, child_key=None, foreign_key=None, 106 title=None, has_default=None):
107 """ 108 @param child_class: The dbclass, this property's owner has a 109 relationship to 110 @param child_key: A string or a tuple of strings indicating those 111 properties of the child_class that are used in the foreign key. This 112 defaults to the child's primary key. The child_key must match the 113 foreign_key in the number of its members and the attribute's 114 datatypes. 115 @param foreign_key: The foreign key. Simmlar to a dbclass' primary key 116 this may be a simple string, indication the attribute that manages 117 the foreign key column or a tuple of strings indicating columns 118 that form the foreign key together. If the foreign key parameter is 119 None __init_dbclass__() will try to guess the foreign key name as 120 'child's table'_'primary key column'. 121 122 """ 123 datatype.__init__(self, column=None, title=title, validators=(), 124 has_default=has_default) 125 126 self.child_class = child_class 127 self.child_key = child_key 128 self.foreign_key = foreign_key
129 130 # Most of the following methods are just not needed for relationship. 131 # If a relationship needs them, it will redefine them anyway.
132 - def __set_from_result__(self, ds, dbobj, value):
133 raise NotImplementedError("Not implemented by this relationship.")
134
135 - def isset(self, dbobj):
136 """ 137 @returns: True. Most relationships are always set, even if they 138 return [] or the like. 139 """ 140 return True
141
142 - def __convert__(self, value):
143 "Relationships do not need a convert method or can't use it anyway." 144 raise NotImplementedError(__doc__)
145
146 - def sql_literal(self, dbobj):
147 "This relationship cannot be represented as an SQL literal." 148 return None
149
150 - def __select_this_column__(self):
151 """ 152 @returns: False. Most relationships do not need to select anything. 153 """ 154 return False
155
156 - def __select_after_insert__(self, dbobj):
157 """ 158 @returns: False. Most relationships to not need to select anything, 159 even after the insert. 160 """ 161 return False
162 163
164 -class _2many(relationship):
165
166 - class result(object):
167 - def __init__(self, dbobj, relationship):
168 """ 169 @param dbobj: The dbobj our parent is a property of. 170 @param relationship: The parent one2many relationship. 171 """ 172 self.dbobj = dbobj 173 self.relationship = relationship
174 175
176 - def __iter__(self):
177 """ 178 Return those child objects that are associated with the parent's 179 owner through their foreign key. 180 """ 181 for dbobj in self.select(): 182 yield dbobj
183
184 - def select(self, *clauses):
185 raise NotImplementedError()
186
187 - def len(self, *claises):
188 raise NotImplementedError()
189
190 - def append(self, *new_child_objects):
191 raise NotImplementedError()
192
193 - def __len__(self):
194 """ 195 Return the number of child dbobjects returned (='contained') in 196 this result. Note that a call to this function will yield a SQL 197 query seperate from the one used to retrieve the the actual 198 objects on every call. 199 """ 200 return int(self.len()) # some backends return long instead of int
201
202 - def ds(self):
203 return self.dbobj.__ds__()
204
205 - def child_class(self):
206 return self.relationship.child_class
207
208 - def where(self):
209 return self.foreign_key.other_where()
210
211 - def add_where(self, clauses):
212 """ 213 Add our where clause to clauses. If there is a where clause 214 among the clauses already, it will be connected to our where 215 clause using AND. 216 217 @returns: A new set of clauses including the new where clause. 218 """ 219 clauses = list(clauses) 220 where = False 221 for counter, clause in enumerate(clauses): 222 if isinstance(clause, sql.where): 223 where = True 224 clauses[counter] = self.where() + clause 225 226 if not where: 227 clauses.append(self.where()) 228 229 return clauses
230
231 - def __getattr__(self, name):
232 """ 233 This function allows access to a dbclass' __result__'s views 234 through a relationship dbattribute. 235 236 >>> view = parent.relationship.view() 237 238 Will return an instance of child.__result__.view initialized 239 to the current result. 240 """ 241 dbclass = self.relationship.child_class 242 243 if hasattr(dbclass.__result__, name): 244 view_cls = getattr(dbclass.__result__, name) 245 if type(view_cls) == ClassType and issubclass(view_cls, view): 246 return view_cls(result=self.select()) 247 248 else: 249 raise AttributeError(name)
250 251
252 - def __init_dbclass__(self, dbclass, attribute_name):
253 # self.dbclass = dbclass 254 # self.attribute_name = attribute_name 255 256 # if self.foreign_key_column is None: 257 # self.foreign_key_column = "%s_%s" % (dbclass.__relation__, 258 # child_class.__primary_key__,) 259 260 # if self.own_key is None: 261 # self.own_key = dbclass.__dict__[dbclass.__primary_key__] 262 263 # # We do not manage a column in the owner's table 264 # # nor a piece of data in the owner inself. 265 # # Deleting them will cause error messages on access attempts ;) 266 # if hasattr(self, "column"): del self.column 267 # if hasattr(self, "_data_attribute_name"): 268 # del self._data_attribute_name 269 pass
270
271 - def __get__(self, dbobj, owner):
272 return self.result(dbobj, self)
273
274 - def __set__(self, dbobj, value):
275 raise NotImplementedError()
276
277 -class one2many(_2many):
278 """ 279 A one2many relationship is probably the most common relationship between 280 two tables in a RDBMS. For each row in table A table B contains zero or 281 more rows. This is implemented by defining a column in table B that 282 contains values that uniquly specify the row in table A they belong to. 283 This is called a 'foreign key', i.e. a key that belongs to a foreign 284 table. 285 """ 286
287 - class result(_2many.result):
288 - def __init__(self, dbobj, relationship):
289 """ 290 @param dbobj: The dbobj our parent is a property of. 291 @param relationship: The parent one2many relationship. 292 """ 293 _2many.result.__init__(self, dbobj, relationship) 294 295 if relationship.child_key is None: 296 # guess the child key name 297 298 try: 299 pkey_column = dbobj.__primary_key__.column() 300 except SimplePrimaryKeyNeeded: 301 msg = "Can't guess foreign key column "+\ 302 "name for multiple column keys %s" 303 info = repr(tuple(dbobj.__primary_key__.attribute_names())) 304 SimplePrimaryKeyNeeded(msg % info) 305 306 child_key = "%s_%s" % ( dbobj.__relation__.name, 307 pkey_column.name, ) 308 309 else: 310 child_key = relationship.child_key 311 312 if relationship.foreign_key is None: 313 foreign_key = tuple(dbobj.__primary_key__.attribute_names()) 314 else: 315 foreign_key = relationship.foreign_key 316 317 self.foreign_key = keys.foreign_key(dbobj, 318 relationship.child_class, 319 foreign_key, 320 child_key)
321 - def select(self, *clauses):
322 """ 323 This method allows you to add your own clauses to the SELECT 324 SQL statement that is used to retrieve the childobjects from the 325 database. This works just like the datasource's select() method, 326 with two exceptions: 327 328 - The dbclass is the relation's childclass. 329 - If you supply a WHERE clause it will be added to the WHERE 330 clause generated by the one2many relationship using the AND 331 conjunction. This WHERE clause is available through this 332 class' where() method. 333 334 Example:: 335 336 country.cities.select(order_by='name') 337 338 """ 339 clauses = self.add_where(clauses) 340 return self.ds().select(self.child_class(), *clauses)
341
342 - def len(self, *clauses):
343 """ 344 Return the number of child objects that would be returned by 345 the select() method using clauses. Note that a call to this 346 function will yield a SQL query seperate from the one used to 347 actually retrieve the dbobjects. (See datasource_base.count() for 348 details) 349 """ 350 clauses = self.add_where(clauses) 351 return self.ds().count(self.child_class(), *clauses)
352
353 - def append(self, *new_child_objects):
354 """ 355 Append new child objects to a one2many relationship. Those 356 objects must not have been inserted into the database, yet, 357 because otherwise they would just be moved from one parent object 358 to the other... 359 """ 360 for a in new_child_objects: 361 if a.__class__ != self.child_class(): 362 raise TypeError("You can only add %s instances to this " +\ 363 "relationship" % repr(self.child_class())) 364 365 if a.__is_stored__(): 366 raise ValueError("You can't append objects to a one2many"+\ 367 " relationship that are already stored"+\ 368 " in the database.") 369 370 for a in new_child_objects: 371 items = zip(self.foreign_key.other_attribute_names(), 372 self.foreign_key.values()) 373 for (attribute_name, value) in items: 374 setattr(a, attribute_name, value) 375 376 self.ds().insert(a)
377
378 - def __init_dbclass__(self, dbclass, attribute_name):
379 pass
380
381 - def __set__(self, dbobj, value):
382 """ 383 Setting a one2many relationship needs three steps: 384 385 1. value must be a list of new (not-yet inserted) child objects 386 2. delete all child objects from the db 387 3. insert all child objects on the list with the foreign key 388 pointing to dbobj 389 """ 390 raise NotImplementedError("Not implemented, yet")
391 392 393
394 -class many2one(relationship):
395 - def __init__(self, child_class, child_key=None, foreign_key=None, 396 title=None, has_default=None):
397 398 relationship.__init__(self, child_class, child_key, foreign_key, 399 title, has_default) 400 401 if self.child_key is None: 402 child_pkey = keys.primary_key(child_class) 403 self.child_key = child_pkey.attribute_names() 404 405 if self.foreign_key is None: 406 foreign_key_name = "%s_%s" % ( self.child_class.__name__, 407 child_pkey.attribute_name(), ) 408 self.foreign_key = ( foreign_key_name, )
409 410
411 - def __set__(self, dbobj, value):
412 foreign_key = keys.foreign_key(dbobj, 413 self.child_class, 414 self.foreign_key, 415 self.child_key) 416 417 if isinstance(value, self.child_class): 418 if not value.__is_stored__(): 419 raise ObjectMustBeInserted("To set a many2one/one2one value " 420 "the child object must have been " 421 "inserted into the database.") 422 setattr(dbobj, self.data_attribute_name(), value) 423 424 for my_attr, child_attr in zip(foreign_key.my_attribute_names(), 425 foreign_key.other_attribute_names()): 426 setattr(dbobj, my_attr, getattr(value, child_attr)) 427 428 elif value is None: 429 for my_attr in foreign_key.my_attribute_names(): 430 setattr(dbobj, my_attr, None) 431 432 dbobj.__ds__().flush_updates() 433 434 else: 435 msg = "The %s attribute can only be set to objects of class %s" 436 msg = msg % ( self.attribute_name, self.child_class.__name__, ) 437 raise ValueError(msg)
438
439 - def __get__(self, dbobj, owner):
440 if self.isset(dbobj): 441 # If the this has run before the value has been cached 442 if hasattr(dbobj, self.data_attribute_name()): 443 return getattr(dbobj, self.data_attribute_name()) 444 else: 445 foreign_key = keys.foreign_key(dbobj, 446 self.child_class, 447 self.foreign_key, 448 self.child_key) 449 450 child = dbobj.__ds__().select_one(self.child_class, 451 foreign_key.other_where()) 452 453 setattr(dbobj, self.data_attribute_name(), child) 454 455 return child 456 457 else: 458 # this will raise an exception (w/ a complicated error message) 459 relationship.__get__(self, dbobj, owner)
460 461 one2one = many2one 462
463 -class many2many(_2many):
464 """ 465 The many to many relationship manages rows from two tables which 466 are linked by means of a third table, the link_relation. This tables 467 stores keys from each of the tables that are to be linked. Note that 468 these keys must be single column keys. 469 470 If you set a many2many property to a list of child objects or append one 471 it will be inserted into the databse if need be. 472 473 The result class implements a subset of the list interface, but the 474 lists elements are considered to have no guaranteed order (as the values 475 in a dict have no order). 476 """ 477
478 - class result(_2many.result):
479 """ 480 Instances of this class are returned if you __get__ a many2many 481 dbproperty. 482 """
483 - def select(self, *clauses):
484 """ 485 Use like this: 486 487 >>> result = dbobj.children.select(sql.where(...)) 488 489 This will yield those child objects that fit the condition 490 in the where statements. Note that your WHERE will be 491 integrated into a more complex WHERE clause. The many2many 492 relationship uses a LEFT JOIN to connect the link_relation 493 and the child relation. You must do that by hand. Also, 494 doing so might mess up your db, so you might want to use 495 FOREIGN KEY constraints on the link relation. 496 """ 497 relations = ( self.relationship.link_relation, 498 self.child_class().__relation__, ) 499 clauses = self.add_where(clauses) 500 501 query = sql.select( 502 self.child_class().__select_columns__(), 503 relations, *clauses) 504 505 return self.ds().run_select( 506 self.child_class(), query)
507 508
509 - def len(self, *clauses):
510 """ 511 Return the number of child objects associated with a parent. 512 You may supply a where clause. The same things apply as for the 513 where clause for select(), see above. 514 """ 515 clauses = self.add_where(clauses) 516 517 query = sql.select("COUNT(*)", 518 ( self.relationship.link_relation, 519 self.child_class().__relation__, ), 520 *clauses) 521 522 return self.ds().query_one(query)
523
524 - def where(self):
525 """ 526 Return the WHERE clause that selects the child objects for the 527 given parent. The clause will also include the condition to limit 528 the JOIN to the appropriate rows. 529 """ 530 parent_where = sql.where( 531 sql.column(self.relationship.parent_link_column(self.dbobj), 532 relation=self.relationship.link_relation), 533 " = ", 534 self.dbobj.__primary_key__.sql_literal()) 535 536 join_where = sql.where( 537 sql.column(self.child_class().__primary_key__, 538 self.child_class().__relation__), 539 " = ", 540 sql.column(self.relationship.child_link_column(), 541 self.relationship.link_relation)) 542 543 return join_where + parent_where
544 545
546 - def all(self, *clauses):
547 """ 548 This method will return all entries in the child relation (or a 549 subset specified by clauses) and a list of those primary keys 550 which are present in the link table. You can check if a dbobj is 551 linked by doing: 552 553 >>> result, active_keys = dbobj.relation.all() 554 >>> for a in result: 555 ... if a.__primary_key__.values() in active_keys: 556 ... do_something(a) 557 ... else: 558 ... do_somethin_else(a) 559 560 561 """ 562 if self.dbobj.__is_stored__(): 563 relations = ( self.relationship.link_relation, 564 self.child_class().__relation__, ) 565 join_clauses = self.add_where(clauses) 566 567 child_pkey = keys.primary_key(self.child_class()) 568 query = sql.select( tuple(child_pkey.columns()), 569 relations, *join_clauses) 570 571 cursor = self.ds().execute(query) 572 active_keys = list(cursor.fetchall()) 573 else: 574 active_keys = [] 575 576 result = self.ds().select(self.child_class(), *clauses) 577 578 return ( result, active_keys, )
579
580 - def append(self, *new_child_objects):
581 """ 582 Appends new child objects to the parent's many2many dbproperty. 583 """ 584 for dbobj in new_child_objects: 585 if not isinstance(dbobj, self.child_class()): 586 msg = "This relationship can only handle %s" % \ 587 repr(self.child_class()) 588 raise TypeError(msg) 589 590 if not dbobj.__is_stored__(): 591 # The many2many relationship will insert fresh objects 592 # into the database. 593 self.ds().insert(dbobj) 594 595 # insert a row into the link_relation 596 command = sql.insert(self.relationship.link_relation, 597 ( self.relationship.parent_link_column(self.dbobj), 598 self.relationship.child_link_column(), ), 599 ( self.relationship.parent_own_key( 600 self.dbobj).sql_literal(self.dbobj), 601 self.relationship.child_own_key().sql_literal(dbobj), )) 602 603 self.ds().execute(command)
604 612
633 634 635
636 - def __init__(self, child_class, link_relation, 637 parent_own_key=None, parent_link_column=None, 638 child_own_key=None, child_link_column=None, 639 title=None):
640 """ 641 @param parent_own_key: The attribute in the parent dbclass that 642 is referred to by the link table. Defaults to the primary key. 643 @param parent_link_column: The column name(!) in the link table 644 referring to parent_own_key. Defaults to 645 <parent class name>_<primary key column>. 646 @param child_own_key: The attribute in the child dbclass that 647 is referred to by the link table. Defaults to the primary key. 648 @param child_link_column: The column name(!) in the link table 649 referring to child_own_key. Defaults to 650 <child class name>_<child key column>. 651 """ 652 relationship.__init__(self, child_class, None, None, 653 title, False) 654 655 if isinstance(link_relation, sql.relation): 656 self.link_relation = link_relation 657 else: 658 self.link_relation = sql.relation(link_relation) 659 660 self._parent_own_key = parent_own_key 661 662 if isinstance(parent_link_column, sql.column): 663 self._parent_link_column = parent_link_column.column 664 else: 665 self._parent_link_column = parent_link_column 666 667 self._child_own_key = child_own_key 668 669 if isinstance(child_link_column, sql.column): 670 self._child_link_column = child_link_column.column 671 else: 672 self._child_link_column = child_link_column
673 674
675 - def __set__(self, dbobj, value):
676 # make sure value is a sequence. 677 try: 678 value = list(value) 679 except TypeError: 680 raise ValueError("You must assing a sequence of %s to this dbproperty!" % repr(self.child_class)) 681 682 # delete all links from the link_relation that point to the dbobj 683 command = sql.delete(self.link_relation, 684 sql.where(self.parent_link_column(dbobj), 685 " = ", 686 dbobj.__primary_key__.sql_literal())) 687 dbobj.__ds__().execute(command) 688 689 # use the result class to (re-)insert the links 690 result = self.result(dbobj, self) 691 692 result.append(*value)
693
694 - def reverse(cls, original_dbclass, attribute_name, 695 title=None):
696 """ 697 Constructor. 698 699 A little helper function: If you've defined one many2many relation, 700 this constructor will take it as an argument and return the 701 complimentary one to be an attribute in the child class. 702 703 @param original_dbclass: dbclass you've already defined a many2many 704 relationship for. 705 @param attribute_name: Attribute name of the many2many relationship 706 in the original dbclass. 707 """ 708 709 original = original_dbclass.__dbproperty__(attribute_name) 710 711 return many2many(original_dbclass, 712 original.link_relation, 713 original._child_own_key, 714 original._child_link_column, 715 original._parent_own_key, 716 original._parent_link_column, 717 title)
718 719 reverse = classmethod(reverse) 720
721 - def parent_own_key(self, dbobj):
722 if self._parent_own_key is None: 723 return dbobj.__primary_key__.attribute() 724 else: 725 return dbobj.__dbproperty__(self._parent_own_key)
726 733
734 - def child_own_key(self):
735 if self._child_own_key is None: 736 return self.child_class.__dbproperty__() 737 else: 738 return self.child_class.__dbproperty__(self._child_own_key)
739
749 750 751 752
753 -class many2one(relationship):
754 - def __init__(self, child_class, 755 child_key=None, foreign_key=None, column=None, 756 title=None, has_default=None, 757 cache=True):
758 """ 759 @param cache: Indicates whether the relationship object shall 760 keep a copy of the child object in the dbobj for faster access. 761 """ 762 if foreign_key is not None and column is not None: 763 raise Exception("You can't specify both a foreign_key (based"+\ 764 " on attribute names) and an SQL column for "+\ 765 " a many2one relationship") 766 767 datatype.__init__(self, column, title, (), has_default) 768 769 if foreign_key is None: 770 pk = keys.primary_key(child_class) 771 self.python_class = pk.attribute().python_class 772 self.sql_literal_class = pk.attribute().sql_literal_class 773 774 self.child_class = child_class 775 776 if child_key is None: 777 self.child_key = child_class.__primary_key__ 778 else: 779 self.child_key = child_key 780 781 self.foreign_key = foreign_key 782 783 self.cache = cache
784
785 - def __set_from_result__(self, ds, dbobj, value):
786 setattr(dbobj, self.data_attribute_name(), value)
787
788 - def __init_dbclass__(self, dbclass, attribute_name):
789 if self.column is None and self.foreign_key is None: 790 column_name = "%s_%s" % ( self.child_class.__name__, 791 self.child_class.__primary_key__) 792 # A class' __primary_key__ attr is always a string! 793 794 self.column = sql.column(column_name) 795 796 datatype.__init_dbclass__(self, dbclass, attribute_name) 797 798 if self.foreign_key is not None: 799 self.column = None
800 801 802
803 - def __get__(self, dbobj, owner):
804 if self.cache and \ 805 hasattr(dbobj, self.data_attribute_name() + "_cache"): 806 return getattr(dbobj, self.data_attribute_name() + "_cache") 807 808 ds = dbobj.__ds__() 809 810 if self.column is not None: 811 value = datatype.__get__(self, dbobj) 812 ret = ds.select_by_primary_key(self.child_class, value) 813 else: 814 foreign_key = keys.foreign_key(dbobj, self.child_class, 815 self.foreign_key, self.child_key) 816 817 # Ok, let's check some exception condition... 818 set_count = 0 819 none_count = 0 820 for attr in foreign_key.my_attributes(): 821 if attr.__get__(dbobj) is None: 822 none_count += 1 823 else: 824 set_count += 1 825 826 if set_count == 0: 827 return None # a many2one attribute with a multi column key 828 # is considered None, if all key attributes are 829 # None 830 831 832 if none_count != 0: 833 # If some of the key attributes are None, and some are set, 834 # it is considered an error 835 raise IllegalForeignKey("For a many2one relationship with a "+\ 836 "multi column key, either all attrs "+\ 837 "must be set or all must be None.") 838 839 result = ds.select(self.child_class, 840 foreign_key.other_where()) 841 842 try: 843 ret = result.next() 844 except StopIteration: 845 raise IllegalForeignKey("The foreign key you set for " + \ 846 repr(dbobj) + " does not refer to " + \ 847 "exaxtly one child object") 848 849 if self.cache: 850 setattr(dbobj, self.data_attribute_name() + "_cache", ret) 851 852 return ret
853
854 - def __set__(self, dbobj, value):
855 """ 856 Set the child object to `value'. If the child object has not been 857 inserted, yet it will be by this function. 858 """ 859 if value is None: 860 if self.column is not None: 861 datatype.__set__(self, dbobj, None) 862 else: 863 foreign_key = keys.foreign_key(dbobj, self.child_class, 864 self.foreign_key, 865 self.child_key) 866 867 for prop in foreign_key.my_attributes(): 868 prop.__set__(dbobj, None) 869 870 if hasattr(dbobj, self.data_attribute_name() + "_cache"): 871 delattr(dbobj, self.data_attribute_name() + "_cache") 872 else: 873 if not isinstance(value, self.child_class): 874 raise TypeError("A many2one attribute can only be set to " + \ 875 self.child_class.__name__, " + instances") 876 877 if not value.__is_stored__(): 878 self.__ds__().insert(value) 879 880 if self.column is not None: 881 datatype.__set__(self, dbobj, value.__primary_key__. 882 attribute().__get__(value)) 883 else: 884 foreign_key = keys.foreign_key(dbobj, self.child_class, 885 self.foreign_key, 886 self.child_key) 887 888 for my, other in zip(foreign_key.my_attributes(), 889 foreign_key.other_attributes()): 890 my.__set__(dbobj, other.__get__(value)) 891 892 if self.cache: 893 setattr(dbobj, self.data_attribute_name() + "_cache", value)
894 895
896 - def isset(self, dbobj):
897 if self.column is not None: 898 return datatype.isset(self, dbobj) 899 else: 900 foreign_key = keys.foreign_key(dbobj, self.child_class, 901 self.foreign_key, self.child_key) 902 903 return foreign_key.isset()
904
905 - def __convert__(self, value):
906 if not isinstance(self.child_class.__primary_key__, keys.primary_key): 907 return value 908 else: 909 return self.child_class.__primary_key__.attributes().\ 910 __convert__(value)
911
912 - def sql_literal(self, dbobj):
913 if self.column is None: 914 return None # We don't INSERT anything 915 else: 916 return datatype.sql_literal(self, dbobj)
917
918 - def __select_this_column__(self):
919 if self.column is not None: 920 return True 921 else: 922 return False
923 924 925 926 927 # Local variables: 928 # mode: python 929 # ispell-local-dictionary: "english" 930 # End: 931