Changeset 458
- Timestamp:
- 09/17/09 13:54:19 (4 years ago)
- Location:
- elixir/trunk
- Files:
-
- 4 modified
-
CHANGES (modified) (4 diffs)
-
elixir/options.py (modified) (2 diffs)
-
elixir/relationships.py (modified) (5 diffs)
-
tests/test_m2m.py (modified) (6 diffs)
Legend:
- Unmodified
- Added
- Removed
-
elixir/trunk/CHANGES
r448 r458 2 2 3 3 Please see http://elixir.ematia.de/trac/wiki/Migrate06to07 for detailed 4 upgrade notes. 4 upgrade notes. If you are upgrading an application with existing data from an 5 earlier version of Elixir, you are VERY STRONGLY ADVISED to read them! 5 6 6 7 New features: … … 20 21 ManyToMany table in a custom schema and not necessarily the same schema as 21 22 the table of the "source" entity (patch from Diez B. Roggisch). 22 - Added a new "target_column" argument on ManyToOne relationships (ticket #26). 23 TODOC 23 - Added a new "target_column" argument on ManyToOne relationships so that you 24 can target unique but non-primary key columns. At the moment, this only works 25 if the target column is declared before the ManyToOne (see ticket #26). 24 26 - Added new column_names argument to the acts_as_versioned extension, allowing 25 27 to specify custom column names (inspired by a patch by Alex Bodnaru). … … 49 51 50 52 Bug fixes: 53 - Changed the pattern used to generate column names for all ManyToMany 54 relationships so that the meaning of bidirectional 55 self-referential relationships does not depend on the order of declaration 56 of each side (closes #69). See upgrade notes for details. 57 - Changed slightly the pattern used to generate the name of the table for 58 bidirectional self-referential ManyToMany relationships so that it doesn't 59 depend on the order of declaration of each side (closes #19). 60 See upgrade notes for details. 51 61 - default EntityCollection raise an exception instead of returning None when 52 62 trying to resolve an inexisting Entity from outside of another entity (for 53 63 example through EntityCollection.__getattr__ 54 - Changed slightly the algorithm to generate the name of the table for55 bidirectional self-referential ManyToMany relationships so that it doesn't56 depend on the order of declaration of each side (closes #19). If you are57 upgrading an application with existing data from an earlier version of58 Elixir, you are STRONGLY ADVISED to read the upgrade notes!59 =================60 TEMPORARY NOTICE:61 =================62 For now it is even worse: the table works but the relationship's meaning is63 reversed. Will be fixed before 0.7 ships (see ticket #69).64 =================65 64 - Fixed the case where you specify both "primaryjoin" and "colname" arguments 66 65 (useless in this case, but harmless) on a ManyToOne relationship of an … … 72 71 secondaryjoin as not been specified manually. 73 72 - Added missing documentation for the "filter" argument on OneToMany 74 relationships 73 relationships. 75 74 - Fixed the act_as_list extension's move_to_bottom method to work on MySQL 76 75 (closes #34). 77 76 - Fixed event methods not being called when they are defined on a parent class. 78 (introduced in r 262).77 (introduced in release 0.5.0). 79 78 - Added workaround for an odd mod_python behavior (class.__module__ returns a 80 79 weird name which is not in sys.modules). -
elixir/trunk/elixir/options.py
r437 r458 184 184 # format constants 185 185 FKCOL_NAMEFORMAT = "%(relname)s_%(key)s" 186 M2MCOL_NAMEFORMAT = "%(tablename)s_%(key)s" 186 OLD_M2MCOL_NAMEFORMAT = "%(tablename)s_%(key)s%(numifself)s" 187 NEW_M2MCOL_NAMEFORMAT = "%(relname)s_%(key)s" 188 M2MCOL_NAMEFORMAT = NEW_M2MCOL_NAMEFORMAT 187 189 CONSTRAINT_NAMEFORMAT = "%(tablename)s_%(colnames)s_fk" 188 190 MULTIINHERITANCECOL_NAMEFORMAT = "%(entity)s_%(key)s" … … 197 199 198 200 # debugging/migration help 199 CHECK_TABLENAME_CHANGES= False201 MIGRATION_TO_07_AID = False 200 202 201 203 # -
elixir/trunk/elixir/relationships.py
r455 r458 417 417 __doc_all__ = [] 418 418 419 419 420 class Relationship(Property): 420 421 ''' … … 862 863 "all ManyToMany tables", DeprecationWarning, stacklevel=3) 863 864 self.column_format = column_format or options.M2MCOL_NAMEFORMAT 865 if not callable(self.column_format): 866 # we need to store the format in a variable so that the 867 # closure of the lambda is correct 868 format = self.column_format 869 self.column_format = lambda data: format % data 870 if options.MIGRATION_TO_07_AID: 871 self.column_format = \ 872 migration_aid_column_formatter(self.column_format) 864 873 865 874 self.filter = filter … … 938 947 # whether this relation or its inverse is setup first). 939 948 if self.inverse and source_part < target_part: 940 #TODO: if self-relf, only include source_part 949 #XXX: use a different scheme for selfref (to not include the 950 # table name twice)? 941 951 tablename = "%s__%s" % (target_part, source_part) 942 if options. CHECK_TABLENAME_CHANGESand \952 if options.MIGRATION_TO_07_AID and \ 943 953 e1_desc.tablename >= e2_desc.tablename: 944 954 oldname = "%s__%s" % (source_part, target_part) 945 946 raise Exception( 955 warnings.warn( 947 956 "The generated table name for the '%s' relationship " 948 957 "on the '%s' entity changed from '%s' (the name " … … 996 1005 assert len(colnames) == len(desc.primary_keys) 997 1006 else: 1007 data = {# relationship info 1008 'relname': rel and rel.name or 'inverse', 1009 'selfref': e1_desc is e2_desc, 1010 'num': num, 1011 'numifself': e1_desc is e2_desc and str(num + 1) 1012 or '', 1013 # target info 1014 'target': desc.entity, 1015 'entity': desc.entity.__name__.lower(), 1016 'tablename': desc.tablename 1017 } 1018 colnames = [] 998 1019 for pk_col in desc.primary_keys: 999 colname = self.column_format % \ 1000 {'tablename': desc.tablename, 1001 'key': pk_col.key, 1002 'entity': desc.entity.__name__.lower()} 1003 # In case we have a many-to-many self-reference, we 1004 # need to tweak the names of the columns so that we 1005 # don't end up with twice the same column name. 1006 if self.entity is self.target: 1007 colname += str(num + 1) 1008 colnames.append(colname) 1020 data.update(key=pk_col.key) 1021 colnames.append(self.column_format(data)) 1009 1022 1010 1023 for pk_col, colname in zip(desc.primary_keys, colnames): … … 1087 1100 1088 1101 1102 def alternate_m2m_column_formatter(data): 1103 if data['selfref']: 1104 return options.NEW_M2MCOL_NAMEFORMAT % data 1105 else: 1106 return options.OLD_M2MCOL_NAMEFORMAT % data 1107 1108 1109 def migration_aid_m2m_column_formatter(formatter): 1110 def debug_formatter(data): 1111 new_name = formatter(data) 1112 old_name = options.OLD_M2MCOL_NAMEFORMAT % data 1113 if new_name != old_name: 1114 complete_data = data.copy() 1115 #TODO: use explicit num and selfref variables 1116 complete_data.update(old_name=old_name, 1117 new_name=new_name, 1118 dir=data['num'] is 0 and 'local' or 'remote') 1119 # Specifying a stacklevel is useless in this case as the name 1120 # generation is triggered by setup_all(), not by the declaration 1121 # of the offending relationship. 1122 warnings.warn("The generated column name for the '%(relname)s' " 1123 "relationship on the '%(entity)s' entity changed " 1124 "from '%(old_name)s' to '%(new_name)s'. " 1125 % complete_data) 1126 return new_name 1127 return debug_formatter 1128 1129 1089 1130 def _get_join_clauses(local_table, local_cols1, local_cols2, target_table): 1090 1131 primary_join, secondary_join = [], [] -
elixir/trunk/tests/test_m2m.py
r451 r458 4 4 5 5 from elixir import * 6 import elixir 6 7 7 8 #----------- … … 24 25 25 26 setup_all(True) 26 27 A.mapper.compile() 28 29 # check m2m table was generated correctly 30 m2m_table = A.bs_.property.secondary 31 assert m2m_table.name in metadata.tables 32 33 # check column names 34 m2m_cols = m2m_table.columns 35 assert 'bs__id' in m2m_cols 36 assert 'as__id' in m2m_cols 37 38 # check the relationships work as expected 27 39 b1 = B(name='b1', as_=[A(name='a1')]) 28 40 … … 36 48 assert b in a.bs_ 37 49 38 def test_column_format(self): 50 def test_custom_global_column_nameformat(self): 51 # this needs to be done before declaring the classes 52 elixir.options.M2MCOL_NAMEFORMAT = elixir.options.OLD_M2MCOL_NAMEFORMAT 53 54 class A(Entity): 55 bs_ = ManyToMany('B') 56 57 class B(Entity): 58 as_ = ManyToMany('A') 59 60 setup_all(True) 61 62 # revert to original format 63 elixir.options.M2MCOL_NAMEFORMAT = elixir.options.NEW_M2MCOL_NAMEFORMAT 64 65 # check m2m table was generated correctly 66 A.mapper.compile() 67 m2m_table = A.bs_.property.secondary 68 assert m2m_table.name in metadata.tables 69 70 # check column names 71 m2m_cols = m2m_table.columns 72 assert '%s_id' % A.table.name in m2m_cols 73 assert '%s_id' % B.table.name in m2m_cols 74 75 def test_alternate_column_formatter(self): 76 # this needs to be done before declaring the classes 77 elixir.options.M2MCOL_NAMEFORMAT = \ 78 elixir.relationships.alternate_m2m_column_formatter 79 80 class A(Entity): 81 as_ = ManyToMany('A') 82 bs_ = ManyToMany('B') 83 84 class B(Entity): 85 as_ = ManyToMany('A') 86 87 setup_all(True) 88 A.mapper.compile() 89 90 # revert to original format 91 elixir.options.M2MCOL_NAMEFORMAT = elixir.options.NEW_M2MCOL_NAMEFORMAT 92 93 # check m2m table column names were generated correctly 94 m2m_cols = A.bs_.property.secondary.columns 95 assert '%s_id' % A.table.name in m2m_cols 96 assert '%s_id' % B.table.name in m2m_cols 97 98 # check selfref m2m table column names were generated correctly 99 m2m_cols = A.as_.property.secondary.columns 100 assert 'as__id' in m2m_cols 101 assert 'inverse_id' in m2m_cols 102 103 def test_manual_column_format(self): 39 104 class A(Entity): 40 105 using_options(tablename='aye') … … 49 114 setup_all(True) 50 115 51 b1 = B(name='b1', as_=[A(name='a1')]) 52 53 session.commit() 54 session.clear() 55 56 a = A.query.one() 57 b = B.query.one() 58 59 assert a in b.as_ 60 assert b in a.bs_ 61 116 # check column names were generated correctly 117 A.mapper.compile() 62 118 m2m_cols = A.bs_.property.secondary.columns 63 119 assert 'a_id' in m2m_cols 64 120 assert 'b_id' in m2m_cols 65 121 122 # check the relationships work as expected 123 b1 = B(name='b1', as_=[A(name='a1')]) 124 125 session.commit() 126 session.clear() 127 128 a = A.query.one() 129 b = B.query.one() 130 131 assert a in b.as_ 132 assert b in a.bs_ 133 66 134 def test_multi_pk_in_target(self): 67 135 class A(Entity): … … 137 205 assert barney in homer.friends 138 206 207 m2m_cols = Person.friends.property.secondary.columns 208 assert 'friends_id' in m2m_cols 209 assert 'inverse_id' in m2m_cols 210 139 211 def test_bidirectional_selfref(self): 140 212 class Person(Entity): … … 159 231 assert homer in barney.friends 160 232 assert barney in homer.friends 233 234 m2m_cols = Person.friends.property.secondary.columns 235 assert 'friends_id' in m2m_cols 236 assert 'is_friend_of_id' in m2m_cols 161 237 162 238 def test_has_and_belongs_to_many(self):
