Ticket #6095: 6095-beta-05.diff
File 6095-beta-05.diff, 18.2 KB (added by , 17 years ago) |
---|
-
AUTHORS
diff --git a/AUTHORS b/AUTHORS index be18d21..49a5b31 100644
a b answer newbie questions, and generally made Django that much better: 133 133 Afonso Fernández Nogueira <fonzzo.django@gmail.com> 134 134 Matthew Flanagan <http://wadofstuff.blogspot.com> 135 135 Eric Floehr <eric@intellovations.com> 136 Eric Florenzano <floguy@gmail.com> 136 137 Vincent Foley <vfoleybourgon@yahoo.ca> 137 138 Rudolph Froger <rfroger@estrate.nl> 138 139 Jorge Gajon <gajon@gajon.org> -
django/core/management/sql.py
diff --git a/django/core/management/sql.py b/django/core/management/sql.py index 15bffce..5430dea 100644
a b def many_to_many_sql_for_model(model, style): 352 352 qn = connection.ops.quote_name 353 353 inline_references = connection.features.inline_fk_references 354 354 for f in opts.many_to_many: 355 if not isinstance(f.rel, generic.GenericRel) :355 if not isinstance(f.rel, generic.GenericRel) and f.creates_table: 356 356 tablespace = f.db_tablespace or opts.db_tablespace 357 357 if tablespace and connection.features.supports_tablespaces and connection.features.autoindexes_primary_keys: 358 358 tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace, inline=True) -
django/core/management/validation.py
diff --git a/django/core/management/validation.py b/django/core/management/validation.py index bc9faae..a84cf35 100644
a b def get_validation_errors(outfile, app=None): 104 104 if r.get_accessor_name() == rel_query_name: 105 105 e.add(opts, "Reverse query name for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) 106 106 107 seen_intermediary_signatures = [] 108 107 109 for i, f in enumerate(opts.many_to_many): 108 110 # Check to see if the related m2m field will clash with any 109 111 # existing fields, m2m fields, m2m related objects or related objects … … def get_validation_errors(outfile, app=None): 113 115 # so skip the next section 114 116 if isinstance(f.rel.to, (str, unicode)): 115 117 continue 118 if hasattr(f.rel, 'through') and f.rel.through != None: 119 intermediary_model = None 120 for model in models.get_models(): 121 if model._meta.module_name == f.rel.through.lower(): 122 intermediary_model = model 123 if intermediary_model == None: 124 e.add(opts, "%s has a manually-defined m2m relationship through a model (%s) which does not exist." % (f.name, f.rel.through)) 125 else: 126 signature = (f.rel.to, cls, intermediary_model) 127 if signature in seen_intermediary_signatures: 128 e.add(opts, "%s has two manually-defined m2m relationships through the same model (%s), which is not possible. Please use a field on your intermediary model instead." % (cls._meta.object_name, intermediary_model._meta.object_name)) 129 else: 130 seen_intermediary_signatures.append(signature) 131 seen_related_fk, seen_this_fk = False, False 132 for field in intermediary_model._meta.fields: 133 if field.rel: 134 if field.rel.to == f.rel.to: 135 seen_related_fk = True 136 elif field.rel.to == cls: 137 seen_this_fk = True 138 if not seen_related_fk or not seen_this_fk: 139 e.add(opts, "%s has a manually-defined m2m relationship through a model (%s) which does not have foreign keys to %s and %s" % (f.name, f.rel.through, f.rel.to._meta.object_name, cls._meta.object_name)) 116 140 117 141 rel_opts = f.rel.to._meta 118 142 rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name() -
django/db/models/fields/related.py
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 7d28ba1..9da7073 100644
a b from django.core import validators 10 10 from django import oldforms 11 11 from django import newforms as forms 12 12 from django.dispatch import dispatcher 13 from new import instancemethod 13 14 14 15 try: 15 16 set … … class ForeignRelatedObjectsDescriptor(object): 262 263 manager.clear() 263 264 manager.add(*value) 264 265 265 def create_many_related_manager(superclass ):266 def create_many_related_manager(superclass, through=False): 266 267 """Creates a manager that subclasses 'superclass' (which is a Manager) 267 268 and adds behavior for many-to-many related objects.""" 268 269 class ManyRelatedManager(superclass): 269 270 def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None, 270 join_table=None, source_col_name=None, target_col_name=None): 271 join_table=None, source_col_name=None, target_col_name=None, 272 through=None): 271 273 super(ManyRelatedManager, self).__init__() 272 274 self.core_filters = core_filters 273 275 self.model = model … … def create_many_related_manager(superclass): 276 278 self.join_table = join_table 277 279 self.source_col_name = source_col_name 278 280 self.target_col_name = target_col_name 281 self.through = through 279 282 self._pk_val = self.instance._get_pk_val() 280 283 if self._pk_val is None: 281 284 raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % model) … … def create_many_related_manager(superclass): 283 286 def get_query_set(self): 284 287 return superclass.get_query_set(self).filter(**(self.core_filters)) 285 288 286 def add(self, *objs):287 self._add_items(self.source_col_name, self.target_col_name, *objs)288 289 # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table290 if self.symmetrical:291 self._add_items(self.target_col_name, self.source_col_name, *objs)292 add.alters_data = True293 294 def remove(self, *objs):295 self._remove_items(self.source_col_name, self.target_col_name, *objs)296 297 # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table298 if self.symmetrical:299 self._remove_items(self.target_col_name, self.source_col_name, *objs)300 remove.alters_data = True301 302 289 def clear(self): 303 290 self._clear_items(self.source_col_name) 304 291 … … def create_many_related_manager(superclass): 375 362 [self._pk_val]) 376 363 transaction.commit_unless_managed() 377 364 365 def add(self, *objs): 366 self._add_items(self.source_col_name, self.target_col_name, *objs) 367 # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table 368 if self.symmetrical: 369 self._add_items(self.target_col_name, self.source_col_name, *objs) 370 add.alters_data = True 371 372 def remove(self, *objs): 373 self._remove_items(self.source_col_name, self.target_col_name, *objs) 374 # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table 375 if self.symmetrical: 376 self._remove_items(self.target_col_name, self.source_col_name, *objs) 377 remove.alters_data = True 378 379 if not through: 380 ManyRelatedManager.add = instancemethod(add, None, ManyRelatedManager) 381 ManyRelatedManager.remove = instancemethod(remove, None, ManyRelatedManager) 382 378 383 return ManyRelatedManager 379 384 380 385 class ManyRelatedObjectsDescriptor(object): … … class ManyRelatedObjectsDescriptor(object): 395 400 # model's default manager. 396 401 rel_model = self.related.model 397 402 superclass = rel_model._default_manager.__class__ 398 RelatedManager = create_many_related_manager(superclass )403 RelatedManager = create_many_related_manager(superclass, self.related.field.rel.through) 399 404 400 405 qn = connection.ops.quote_name 401 406 manager = RelatedManager( … … class ManyRelatedObjectsDescriptor(object): 405 410 symmetrical=False, 406 411 join_table=qn(self.related.field.m2m_db_table()), 407 412 source_col_name=qn(self.related.field.m2m_reverse_name()), 408 target_col_name=qn(self.related.field.m2m_column_name()) 413 target_col_name=qn(self.related.field.m2m_column_name()), 414 through=self.related.field.rel.through 409 415 ) 410 416 411 417 return manager … … class ManyRelatedObjectsDescriptor(object): 414 420 if instance is None: 415 421 raise AttributeError, "Manager must be accessed via instance" 416 422 423 through = getattr(self.related.field.rel, 'through', None) 424 if through: 425 raise AttributeError, "Cannot set values on a ManyToManyField which specifies a through model. Use %s's Manager instead." % through 426 417 427 manager = self.__get__(instance) 418 428 manager.clear() 419 429 manager.add(*value) … … class ReverseManyRelatedObjectsDescriptor(object): 436 446 # model's default manager. 437 447 rel_model=self.field.rel.to 438 448 superclass = rel_model._default_manager.__class__ 439 RelatedManager = create_many_related_manager(superclass )449 RelatedManager = create_many_related_manager(superclass, self.field.rel.through) 440 450 441 451 qn = connection.ops.quote_name 442 452 manager = RelatedManager( … … class ReverseManyRelatedObjectsDescriptor(object): 446 456 symmetrical=(self.field.rel.symmetrical and instance.__class__ == rel_model), 447 457 join_table=qn(self.field.m2m_db_table()), 448 458 source_col_name=qn(self.field.m2m_column_name()), 449 target_col_name=qn(self.field.m2m_reverse_name()) 459 target_col_name=qn(self.field.m2m_reverse_name()), 460 through=self.field.rel.through 450 461 ) 451 462 452 463 return manager … … class ReverseManyRelatedObjectsDescriptor(object): 455 466 if instance is None: 456 467 raise AttributeError, "Manager must be accessed via instance" 457 468 469 through = getattr(self.field.rel, 'through', None) 470 if through: 471 raise AttributeError, "Cannot set values on a ManyToManyField which specifies a through model. Use %s's Manager instead." % through 472 458 473 manager = self.__get__(instance) 459 474 manager.clear() 460 475 manager.add(*value) … … class ManyToManyField(RelatedField, Field): 648 663 filter_interface=kwargs.pop('filter_interface', None), 649 664 limit_choices_to=kwargs.pop('limit_choices_to', None), 650 665 raw_id_admin=kwargs.pop('raw_id_admin', False), 651 symmetrical=kwargs.pop('symmetrical', True)) 666 symmetrical=kwargs.pop('symmetrical', True), 667 through=kwargs.pop('through', None)) 652 668 self.db_table = kwargs.pop('db_table', None) 669 if kwargs['rel'].through: 670 self.creates_table = False 671 assert not self.db_table, "Cannot specify a db_table if an intermediary model is used." 672 else: 673 self.creates_table = True 653 674 if kwargs["rel"].raw_id_admin: 654 675 kwargs.setdefault("validator_list", []).append(self.isValidIDList) 655 676 Field.__init__(self, **kwargs) … … class ManyToManyField(RelatedField, Field): 672 693 673 694 def _get_m2m_db_table(self, opts): 674 695 "Function that can be curried to provide the m2m table name for this relation" 675 if self.db_table: 696 if self.rel.through != None: 697 return get_model(opts.app_label, self.rel.through)._meta.db_table 698 elif self.db_table: 676 699 return self.db_table 677 700 else: 678 701 return '%s_%s' % (opts.db_table, self.name) … … class ManyToManyField(RelatedField, Field): 680 703 def _get_m2m_column_name(self, related): 681 704 "Function that can be curried to provide the source column name for the m2m table" 682 705 # If this is an m2m relation to self, avoid the inevitable name clash 683 if related.model == related.parent_model: 706 if self.rel.through != None: 707 field = related.model._meta.get_related_object(self.rel.through).field 708 attname, column = field.get_attname_column() 709 return column 710 elif related.model == related.parent_model: 684 711 return 'from_' + related.model._meta.object_name.lower() + '_id' 685 712 else: 686 713 return related.model._meta.object_name.lower() + '_id' … … class ManyToManyField(RelatedField, Field): 688 715 def _get_m2m_reverse_name(self, related): 689 716 "Function that can be curried to provide the related column name for the m2m table" 690 717 # If this is an m2m relation to self, avoid the inevitable name clash 691 if related.model == related.parent_model: 718 if self.rel.through != None: 719 field = related.parent_model._meta.get_related_object(self.rel.through).field 720 attname, column = field.get_attname_column() 721 return column 722 elif related.model == related.parent_model: 692 723 return 'to_' + related.parent_model._meta.object_name.lower() + '_id' 693 724 else: 694 725 return related.parent_model._meta.object_name.lower() + '_id' … … class OneToOneRel(ManyToOneRel): 809 840 810 841 class ManyToManyRel(object): 811 842 def __init__(self, to, num_in_admin=0, related_name=None, 812 filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True): 843 filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True, 844 through = None): 813 845 self.to = to 814 846 self.num_in_admin = num_in_admin 815 847 self.related_name = related_name … … class ManyToManyRel(object): 821 853 self.raw_id_admin = raw_id_admin 822 854 self.symmetrical = symmetrical 823 855 self.multiple = True 856 self.through = through 824 857 825 858 assert not (self.raw_id_admin and self.filter_interface), "ManyToManyRels may not use both raw_id_admin and filter_interface" -
django/db/models/options.py
diff --git a/django/db/models/options.py b/django/db/models/options.py index 37ace0a..f0af416 100644
a b from django.conf import settings 2 2 from django.db.models.related import RelatedObject 3 3 from django.db.models.fields.related import ManyToManyRel 4 4 from django.db.models.fields import AutoField, FieldDoesNotExist 5 from django.db.models.loading import get_models, app_cache_ready5 from django.db.models.loading import get_models, get_model, app_cache_ready 6 6 from django.db.models.query import orderlist2sql 7 7 from django.db.models import Manager 8 8 from django.utils.translation import activate, deactivate_all, get_language, string_concat … … class Options(object): 162 162 follow = self.get_follow() 163 163 return [f for f in self.get_all_related_objects() if follow.get(f.name, None)] 164 164 165 def get_related_object(self, from_model): 166 "Gets the RelatedObject which links from from_model to this model." 167 if isinstance(from_model, str): 168 from_model = get_model(self.app_label, from_model) 169 for related_object in self.get_all_related_objects(): 170 if related_object.model == from_model: 171 return related_object 172 return None 173 165 174 def get_data_holders(self, follow=None): 166 175 if follow == None: 167 176 follow = self.get_follow() -
tests/modeltests/invalid_models/models.py
diff --git a/tests/modeltests/invalid_models/models.py b/tests/modeltests/invalid_models/models.py index 8a480a2..191950d 100644
a b class Car(models.Model): 111 111 class MissingRelations(models.Model): 112 112 rel1 = models.ForeignKey("Rel1") 113 113 rel2 = models.ManyToManyField("Rel2") 114 115 class MissingManualM2MModel(models.Model): 116 name = models.CharField(max_length=5) 117 missing_m2m = models.ManyToManyField(Model, through="MissingM2MModel") 118 119 class Person(models.Model): 120 name = models.CharField(max_length=5) 121 122 class Group(models.Model): 123 name = models.CharField(max_length=5) 124 primary = models.ManyToManyField(Person, through="Membership", related_name="primary") 125 secondary = models.ManyToManyField(Person, through="Membership", related_name="secondary") 126 127 class GroupTwo(models.Model): 128 name = models.CharField(max_length=5) 129 primary = models.ManyToManyField(Person, through="Membership") 130 secondary = models.ManyToManyField(Group, through="MembershipMissingFK") 131 132 class Membership(models.Model): 133 person = models.ForeignKey(Person) 134 group = models.ForeignKey(Group) 135 not_default_or_null = models.CharField(max_length=5) 136 137 class MembershipMissingFK(models.Model): 138 person = models.ForeignKey(Person) 114 139 115 140 model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute. 116 141 invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute. … … invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_3' clashes wi 197 222 invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_4' clashes with field 'SelfClashM2M.selfclashm2m'. Add a related_name argument to the definition for 'm2m_4'. 198 223 invalid_models.missingrelations: 'rel2' has m2m relation with model Rel2, which has not been installed 199 224 invalid_models.missingrelations: 'rel1' has relation with model Rel1, which has not been installed 225 invalid_models.group: Group has two manually-defined m2m relationships through the same model (Membership), which is not possible. Please use a field on your intermediary model instead. 226 invalid_models.missingmanualm2mmodel: missing_m2m has a manually-defined m2m relationship through a model (MissingM2MModel) which does not exist. 227 invalid_models.grouptwo: primary has a manually-defined m2m relationship through a model (Membership) which does not have foreign keys to Person and GroupTwo 228 invalid_models.grouptwo: secondary has a manually-defined m2m relationship through a model (MembershipMissingFK) which does not have foreign keys to Group and GroupTwo 200 229 """