Changeset 8136
- Timestamp:
- 07/29/08 07:41:08 (2 months ago)
- Files:
-
- django/trunk/AUTHORS (modified) (1 diff)
- django/trunk/django/contrib/admin/options.py (modified) (1 diff)
- django/trunk/django/contrib/contenttypes/generic.py (modified) (1 diff)
- django/trunk/django/core/management/sql.py (modified) (1 diff)
- django/trunk/django/core/management/validation.py (modified) (2 diffs)
- django/trunk/django/db/models/fields/related.py (modified) (20 diffs)
- django/trunk/docs/admin.txt (modified) (1 diff)
- django/trunk/docs/model-api.txt (modified) (3 diffs)
- django/trunk/tests/modeltests/invalid_models/models.py (modified) (2 diffs)
- django/trunk/tests/modeltests/m2m_through (added)
- django/trunk/tests/modeltests/m2m_through/__init__.py (added)
- django/trunk/tests/modeltests/m2m_through/models.py (added)
- django/trunk/tests/regressiontests/m2m_through_regress (added)
- django/trunk/tests/regressiontests/m2m_through_regress/__init__.py (added)
- django/trunk/tests/regressiontests/m2m_through_regress/models.py (added)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
django/trunk/AUTHORS
r8127 r8136 155 155 Matthew Flanagan <http://wadofstuff.blogspot.com> 156 156 Eric Floehr <eric@intellovations.com> 157 Eric Florenzano <floguy@gmail.com> 157 158 Vincent Foley <vfoleybourgon@yahoo.ca> 158 159 Rudolph Froger <rfroger@estrate.nl> django/trunk/django/contrib/admin/options.py
r8057 r8136 162 162 else: 163 163 if isinstance(db_field, models.ManyToManyField): 164 if db_field.name in self.raw_id_fields: 164 # If it uses an intermediary model, don't show field in admin. 165 if db_field.rel.through is not None: 166 return None 167 elif db_field.name in self.raw_id_fields: 165 168 kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel) 166 169 kwargs['help_text'] = '' django/trunk/django/contrib/contenttypes/generic.py
r7477 r8136 105 105 symmetrical=kwargs.pop('symmetrical', True)) 106 106 107 # By its very nature, a GenericRelation doesn't create a table. 108 self.creates_table = False 109 107 110 # Override content-type/object-id field names on the related class 108 111 self.object_id_field_name = kwargs.pop("object_id_field", "object_id") django/trunk/django/core/management/sql.py
r8133 r8136 354 354 inline_references = connection.features.inline_fk_references 355 355 for f in opts.local_many_to_many: 356 if not isinstance(f.rel, generic.GenericRel):356 if f.creates_table: 357 357 tablespace = f.db_tablespace or opts.db_tablespace 358 358 if tablespace and connection.features.supports_tablespaces: django/trunk/django/core/management/validation.py
r7967 r8136 103 103 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)) 104 104 105 seen_intermediary_signatures = [] 105 106 for i, f in enumerate(opts.local_many_to_many): 106 107 # Check to see if the related m2m field will clash with any … … 113 114 if isinstance(f.rel.to, (str, unicode)): 114 115 continue 115 116 if getattr(f.rel, 'through', None) is not None: 117 if hasattr(f.rel, 'through_model'): 118 from_model, to_model = cls, f.rel.to 119 if from_model == to_model and f.rel.symmetrical: 120 e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.") 121 seen_from, seen_to, seen_self = False, False, 0 122 for inter_field in f.rel.through_model._meta.fields: 123 rel_to = getattr(inter_field.rel, 'to', None) 124 if from_model == to_model: # relation to self 125 if rel_to == from_model: 126 seen_self += 1 127 if seen_self > 2: 128 e.add(opts, "Intermediary model %s has more than two foreign keys to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, from_model._meta.object_name)) 129 else: 130 if rel_to == from_model: 131 if seen_from: 132 e.add(opts, "Intermediary model %s has more than one foreign key to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, rel_from._meta.object_name)) 133 else: 134 seen_from = True 135 elif rel_to == to_model: 136 if seen_to: 137 e.add(opts, "Intermediary model %s has more than one foreign key to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, rel_to._meta.object_name)) 138 else: 139 seen_to = True 140 if f.rel.through_model not in models.get_models(): 141 e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed." % (f.name, f.rel.through)) 142 signature = (f.rel.to, cls, f.rel.through_model) 143 if signature in seen_intermediary_signatures: 144 e.add(opts, "The model %s has two manually-defined m2m relations through the model %s, which is not permitted. Please consider using an extra field on your intermediary model instead." % (cls._meta.object_name, f.rel.through_model._meta.object_name)) 145 else: 146 seen_intermediary_signatures.append(signature) 147 seen_related_fk, seen_this_fk = False, False 148 for field in f.rel.through_model._meta.fields: 149 if field.rel: 150 if not seen_related_fk and field.rel.to == f.rel.to: 151 seen_related_fk = True 152 elif field.rel.to == cls: 153 seen_this_fk = True 154 if not seen_related_fk or not seen_this_fk: 155 e.add(opts, "'%s' has a manually-defined m2m relation through 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)) 156 else: 157 e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed" % (f.name, f.rel.through)) 158 116 159 rel_opts = f.rel.to._meta 117 160 rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name() django/trunk/django/db/models/fields/related.py
r8132 r8136 24 24 pending_lookups = {} 25 25 26 def add_lazy_relation(cls, field, relation ):26 def add_lazy_relation(cls, field, relation, operation): 27 27 """ 28 28 Adds a lookup on ``cls`` when a related field is defined using a string, … … 46 46 lazy relationships -- then the relation won't be set up until the 47 47 class_prepared signal fires at the end of model initialization. 48 49 operation is the work that must be performed once the relation can be resolved. 48 50 """ 49 51 # Check for recursive relations … … 67 69 model = get_model(app_label, model_name, False) 68 70 if model: 69 field.rel.to = model 70 field.do_related_class(model, cls) 71 operation(field, model, cls) 71 72 else: 72 73 key = (app_label, model_name) 73 value = (cls, field )74 value = (cls, field, operation) 74 75 pending_lookups.setdefault(key, []).append(value) 75 76 … … 79 80 """ 80 81 key = (sender._meta.app_label, sender.__name__) 81 for cls, field in pending_lookups.pop(key, []): 82 field.rel.to = sender 83 field.do_related_class(sender, cls) 82 for cls, field, operation in pending_lookups.pop(key, []): 83 operation(field, sender, cls) 84 84 85 85 dispatcher.connect(do_pending_lookups, signal=signals.class_prepared) … … 109 109 other = self.rel.to 110 110 if isinstance(other, basestring): 111 add_lazy_relation(cls, self, other) 111 def resolve_related_class(field, model, cls): 112 field.rel.to = model 113 field.do_related_class(model, cls) 114 add_lazy_relation(cls, self, other, resolve_related_class) 112 115 else: 113 116 self.do_related_class(other, cls) … … 341 344 manager.add(*value) 342 345 343 def create_many_related_manager(superclass ):346 def create_many_related_manager(superclass, through=False): 344 347 """Creates a manager that subclasses 'superclass' (which is a Manager) 345 348 and adds behavior for many-to-many related objects.""" … … 355 358 self.source_col_name = source_col_name 356 359 self.target_col_name = target_col_name 360 self.through = through 357 361 self._pk_val = self.instance._get_pk_val() 358 362 if self._pk_val is None: … … 362 366 return superclass.get_query_set(self).filter(**(self.core_filters)) 363 367 364 def add(self, *objs): 365 self._add_items(self.source_col_name, self.target_col_name, *objs) 366 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 375 # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table 376 if self.symmetrical: 377 self._remove_items(self.target_col_name, self.source_col_name, *objs) 378 remove.alters_data = True 368 # If the ManyToMany relation has an intermediary model, 369 # the add and remove methods do not exist. 370 if through is None: 371 def add(self, *objs): 372 self._add_items(self.source_col_name, self.target_col_name, *objs) 373 374 # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table 375 if self.symmetrical: 376 self._add_items(self.target_col_name, self.source_col_name, *objs) 377 add.alters_data = True 378 379 def remove(self, *objs): 380 self._remove_items(self.source_col_name, self.target_col_name, *objs) 381 382 # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table 383 if self.symmetrical: 384 self._remove_items(self.target_col_name, self.source_col_name, *objs) 385 remove.alters_data = True 379 386 380 387 def clear(self): … … 387 394 388 395 def create(self, **kwargs): 396 # This check needs to be done here, since we can't later remove this 397 # from the method lookup table, as we do with add and remove. 398 if through is not None: 399 raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through 389 400 new_obj = self.model(**kwargs) 390 401 new_obj.save() … … 474 485 rel_model = self.related.model 475 486 superclass = rel_model._default_manager.__class__ 476 RelatedManager = create_many_related_manager(superclass )487 RelatedManager = create_many_related_manager(superclass, self.related.field.rel.through) 477 488 478 489 qn = connection.ops.quote_name … … 493 504 raise AttributeError, "Manager must be accessed via instance" 494 505 506 through = getattr(self.related.field.rel, 'through', None) 507 if through is not None: 508 raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through 509 495 510 manager = self.__get__(instance) 496 511 manager.clear() … … 515 530 rel_model=self.field.rel.to 516 531 superclass = rel_model._default_manager.__class__ 517 RelatedManager = create_many_related_manager(superclass )532 RelatedManager = create_many_related_manager(superclass, self.field.rel.through) 518 533 519 534 qn = connection.ops.quote_name … … 533 548 if instance is None: 534 549 raise AttributeError, "Manager must be accessed via instance" 550 551 through = getattr(self.field.rel, 'through', None) 552 if through is not None: 553 raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through 535 554 536 555 manager = self.__get__(instance) … … 585 604 class ManyToManyRel(object): 586 605 def __init__(self, to, num_in_admin=0, related_name=None, 587 limit_choices_to=None, symmetrical=True ):606 limit_choices_to=None, symmetrical=True, through=None): 588 607 self.to = to 589 608 self.num_in_admin = num_in_admin … … 595 614 self.symmetrical = symmetrical 596 615 self.multiple = True 616 self.through = through 597 617 598 618 class ForeignKey(RelatedField, Field): … … 724 744 related_name=kwargs.pop('related_name', None), 725 745 limit_choices_to=kwargs.pop('limit_choices_to', None), 726 symmetrical=kwargs.pop('symmetrical', True)) 746 symmetrical=kwargs.pop('symmetrical', True), 747 through=kwargs.pop('through', None)) 748 727 749 self.db_table = kwargs.pop('db_table', None) 750 if kwargs['rel'].through is not None: 751 self.creates_table = False 752 assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used." 753 else: 754 self.creates_table = True 755 728 756 Field.__init__(self, **kwargs) 729 757 … … 740 768 def _get_m2m_db_table(self, opts): 741 769 "Function that can be curried to provide the m2m table name for this relation" 742 if self.db_table: 770 if self.rel.through is not None: 771 return self.rel.through_model._meta.db_table 772 elif self.db_table: 743 773 return self.db_table 744 774 else: … … 747 777 def _get_m2m_column_name(self, related): 748 778 "Function that can be curried to provide the source column name for the m2m table" 749 # If this is an m2m relation to self, avoid the inevitable name clash 750 if related.model == related.parent_model: 751 return 'from_' + related.model._meta.object_name.lower() + '_id' 752 else: 753 return related.model._meta.object_name.lower() + '_id' 779 try: 780 return self._m2m_column_name_cache 781 except: 782 if self.rel.through is not None: 783 for f in self.rel.through_model._meta.fields: 784 if hasattr(f,'rel') and f.rel and f.rel.to == related.model: 785 self._m2m_column_name_cache = f.column 786 break 787 # If this is an m2m relation to self, avoid the inevitable name clash 788 elif related.model == related.parent_model: 789 self._m2m_column_name_cache = 'from_' + related.model._meta.object_name.lower() + '_id' 790 else: 791 self._m2m_column_name_cache = related.model._meta.object_name.lower() + '_id' 792 793 # Return the newly cached value 794 return self._m2m_column_name_cache 754 795 755 796 def _get_m2m_reverse_name(self, related): 756 797 "Function that can be curried to provide the related column name for the m2m table" 757 # If this is an m2m relation to self, avoid the inevitable name clash 758 if related.model == related.parent_model: 759 return 'to_' + related.parent_model._meta.object_name.lower() + '_id' 760 else: 761 return related.parent_model._meta.object_name.lower() + '_id' 798 try: 799 return self._m2m_reverse_name_cache 800 except: 801 if self.rel.through is not None: 802 found = False 803 for f in self.rel.through_model._meta.fields: 804 if hasattr(f,'rel') and f.rel and f.rel.to == related.parent_model: 805 if related.model == related.parent_model: 806 # If this is an m2m-intermediate to self, 807 # the first foreign key you find will be 808 # the source column. Keep searching for 809 # the second foreign key. 810 if found: 811 self._m2m_reverse_name_cache = f.column 812 break 813 else: 814 found = True 815 else: 816 self._m2m_reverse_name_cache = f.column 817 break 818 # If this is an m2m relation to self, avoid the inevitable name clash 819 elif related.model == related.parent_model: 820 self._m2m_reverse_name_cache = 'to_' + related.parent_model._meta.object_name.lower() + '_id' 821 else: 822 self._m2m_reverse_name_cache = related.parent_model._meta.object_name.lower() + '_id' 823 824 # Return the newly cached value 825 return self._m2m_reverse_name_cache 762 826 763 827 def isValidIDList(self, field_data, all_data): … … 793 857 794 858 def contribute_to_class(self, cls, name): 795 super(ManyToManyField, self).contribute_to_class(cls, name) 859 super(ManyToManyField, self).contribute_to_class(cls, name) 796 860 # Add the descriptor for the m2m relation 797 861 setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self)) … … 799 863 # Set up the accessor for the m2m table name for the relation 800 864 self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) 801 865 866 # Populate some necessary rel arguments so that cross-app relations 867 # work correctly. 868 if isinstance(self.rel.through, basestring): 869 def resolve_through_model(field, model, cls): 870 field.rel.through_model = model 871 add_lazy_relation(cls, self, self.rel.through, resolve_through_model) 872 elif self.rel.through: 873 self.rel.through_model = self.rel.through 874 self.rel.through = self.rel.through._meta.object_name 875 802 876 if isinstance(self.rel.to, basestring): 803 877 target = self.rel.to django/trunk/docs/admin.txt
r8111 r8136 618 618 ] 619 619 620 Working with Many-to-Many Intermediary Models 621 ---------------------------------------------- 622 623 By default, admin widgets for many-to-many relations will be displayed inline 624 on whichever model contains the actual reference to the `ManyToManyField`. 625 However, when you specify an intermediary model using the ``through`` 626 argument to a ``ManyToManyField``, the admin will not display a widget by 627 default. This is because each instance of that intermediary model requires 628 more information than could be displayed in a single widget, and the layout 629 required for multiple widgets will vary depending on the intermediate model. 630 631 However, we still want to be able to edit that information inline. Fortunately, 632 this is easy to do with inline admin models. Suppose we have the following 633 models:: 634 635 class Person(models.Model): 636 name = models.CharField(max_length=128) 637 638 class Group(models.Model): 639 name = models.CharField(max_length=128) 640 members = models.ManyToManyField(Person, through='Membership') 641 642 class Membership(models.Model): 643 person = models.ForeignKey(Person) 644 group = models.ForeignKey(Group) 645 date_joined = models.DateField() 646 invite_reason = models.CharField(max_length=64) 647 648 The first step in displaying this intermediate model in the admin is to 649 define an inline model for the Membership table:: 650 651 class MembershipInline(admin.TabularInline): 652 model = Membership 653 extra = 1 654 655 This simple example uses the defaults inline form for the Membership model, 656 and shows 1 extra line. This could be customized using any of the options 657 available to inline models. 658 659 Now create admin views for the ``Person`` and ``Group`` models:: 660 661 class PersonAdmin(admin.ModelAdmin): 662 inlines = (MembershipInline,) 663 664 class GroupAdmin(admin.ModelAdmin): 665 inlines = (MembershipInline,) 666 667 Finally, register your ``Person`` and ``Group`` models with the admin site:: 668 669 admin.site.register(Person, PersonAdmin) 670 admin.site.register(Group, GroupAdmin) 671 672 Now your admin site is set up to edit ``Membership`` objects inline from either 673 the ``Person`` or the ``Group`` detail pages. 674 620 675 ``AdminSite`` objects 621 676 ===================== django/trunk/docs/model-api.txt
r8122 r8136 656 656 example:: 657 657 658 help_text="Please use the following format: <em>YYYY-MM-DD</em>."658 help_text="Please use the following format: <em>YYYY-MM-DD</em>." 659 659 660 660 Alternatively you can use plain text and … … 945 945 ======================= ============================================================ 946 946 947 Extra fields on many-to-many relationships 948 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 949 950 **New in Django development version** 951 952 When you're only dealing with simple many-to-many relationships such as 953 mixing and matching pizzas and toppings, a standard ``ManyToManyField`` 954 is all you need. However, sometimes you may need to associate data with the 955 relationship between two models. 956 957 For example, consider the case of an application tracking the musical groups 958 which musicians belong to. There is a many-to-many relationship between a person 959 and the groups of which they are a member, so you could use a ManyToManyField 960 to represent this relationship. However, there is a lot of detail about the 961 membership that you might want to collect, such as the date at which the person 962 joined the group. 963 964 For these situations, Django allows you to specify the model that will be used 965 to govern the many-to-many relationship. You can then put extra fields on the 966 intermediate model. The intermediate model is associated with the 967 ``ManyToManyField`` using the ``through`` argument to point to the model 968 that will act as an intermediary. For our musician example, the code would look 969 something like this:: 970 971 class Person(models.Model): 972 name = models.CharField(max_length=128) 973 974 def __unicode__(self): 975 return self.name 976 977 class Group(models.Model): 978 name = models.CharField(max_length=128) 979 members = models.ManyToManyField(Person, through='Membership') 980 981 def __unicode__(self): 982 return self.name 983 984 class Membership(models.Model): 985 person = models.ForeignKey(Person) 986 group = models.ForeignKey(Group) 987 date_joined = models.DateField() 988 invite_reason = models.CharField(max_length=64) 989 990 When you set up the intermediary model, you explicitly specify foreign 991 keys to the models that are involved in the ManyToMany relation. This 992 explicit declaration defines how the two models are related. 993 994 There are a few restrictions on the intermediate model: 995 996 * Your intermediate model must contain one - and *only* one - foreign key 997 on the target model (this would be ``Person`` in our example). If you 998 have more than one foreign key, a validation error will be raised. 999 1000 * Your intermediate model must contain one - and *only* one - foreign key 1001 on the source model (this would be ``Group`` in our example). If you 1002 have more than one foreign key, a validation error will be raised. 1003 1004 * If the many-to-many relation is a relation on itself, the relationship 1005 must be non-symmetric. 1006 1007 Now that you have set up your ``ManyToManyField`` to use your intermediary 1008 model (Membership, in this case), you're ready to start creating some 1009 many-to-many relationships. You do this by creating instances of the 1010 intermediate model:: 1011 1012 >>> ringo = Person.objects.create(name="Ringo Starr") 1013 >>> paul = Person.objects.create(name="Paul McCartney") 1014 >>> beatles = Group.objects.create(name="The Beatles") 1015 >>> m1 = Membership(person=ringo, group=beatles, 1016 ... date_joined=date(1962, 8, 16), 1017 ... invite_reason= "Needed a new drummer.") 1018 >>> m1.save() 1019 >>> beatles.members.all() 1020 [<Person: Ringo Starr>] 1021 >>> ringo.group_set.all() 1022 [<Group: The Beatles>] 1023 >>> m2 = Membership.objects.create(person=paul, group=beatles, 1024 ... date_joined=date(1960, 8, 1), 1025 ... invite_reason= "Wanted to form a band.") 1026 >>> beatles.members.all() 1027 [<Person: Ringo Starr>, <Person: Paul McCartney>] 1028 1029 Unlike normal many-to-many fields, you *can't* use ``add``, ``create``, 1030 or assignment (i.e., ``beatles.members = [...]``) to create relationships:: 1031 1032 # THIS WILL NOT WORK 1033 >>> beatles.members.add(john) 1034 # NEITHER WILL THIS 1035 >>> beatles.members.create(name="George Harrison") 1036 # AND NEITHER WILL THIS 1037 >>> beatles.members = [john, paul, ringo, george] 1038 1039 Why? You can't just create a relationship between a Person and a Group - you 1040 need to specify all the detail for the relationship required by the 1041 Membership table. The simple ``add``, ``create`` and assignment calls 1042 don't provide a way to specify this extra detail. As a result, they are 1043 disabled for many-to-many relationships that use an intermediate model. 1044 The only way to create a many-to-many relationship with an intermediate table 1045 is to create instances of the intermediate model. 1046 1047 The ``remove`` method is disabled for similar reasons. However, the 1048 ``clear()`` method can be used to remove all many-to-many relationships 1049 for an instance:: 1050 1051 # Beatles have broken up 1052 >>> beatles.members.clear() 1053 1054 Once you have established the many-to-many relationships by creating instances 1055 of your intermediate model, you can issue queries. Just as with normal 1056 many-to-many relationships, you can query using the attributes of the 1057 many-to-many-related model:: 1058 1059 # Find all the groups with a member whose name starts with 'Paul' 1060 >>> Groups.objects.filter(person__name__startswith='Paul') 1061 [<Group: The Beatles>] 1062 1063 As you are using an intermediate table, you can also query on the attributes 1064 of the intermediate model:: 1065 1066 # Find all the members of the Beatles that joined after 1 Jan 1961 1067 >>> Person.objects.filter( 1068 ... group__name='The Beatles', 1069 ... membership__date_joined__gt=date(1961,1,1)) 1070 [<Person: Ringo Starr] 1071 947 1072 One-to-one relationships 948 1073 ~~~~~~~~~~~~~~~~~~~~~~~~ … … 1146 1271 with a single set of fields:: 1147 1272 1148 unique_together = ("driver", "restaurant")1273 unique_together = ("driver", "restaurant") 1149 1274 1150 1275 ``verbose_name`` django/trunk/tests/modeltests/invalid_models/models.py
r7967 r8136 111 111 rel1 = models.ForeignKey("Rel1") 112 112 rel2 = models.ManyToManyField("Rel2") 113 114 class MissingManualM2MModel(models.Model): 115 name = models.CharField(max_length=5) 116 missing_m2m = models.ManyToManyField(Model, through="MissingM2MModel") 117 118 class Person(models.Model): 119 name = models.CharField(max_length=5) 120 121 class Group(models.Model): 122 name = models.CharField(max_length=5) 123 primary = models.ManyToManyField(Person, through="Membership", related_name="primary") 124 secondary = models.ManyToManyField(Person, through="Membership", related_name="secondary") 125 tertiary = models.ManyToManyField(Person, through="RelationshipDoubleFK", related_name="tertiary") 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) 139 140 class PersonSelfRefM2M(models.Model): 141 name = models.CharField(max_length=5) 142 friends = models.ManyToManyField('self', through="Relationship") 143 too_many_friends = models.ManyToManyField('self', through="RelationshipTripleFK") 144 145 class PersonSelfRefM2MExplicit(models.Model): 146 name = models.CharField(max_length=5) 147 friends = models.ManyToManyField('self', through="ExplicitRelationship", symmetrical=True) 148 149 class Relationship(models.Model): 150 first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set") 151 second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set") 152 date_added = models.DateTimeField() 153 154 class ExplicitRelationship(models.Model): 155 first = models.ForeignKey(PersonSelfRefM2MExplicit, related_name="rel_from_set") 156 second = models.ForeignKey(PersonSelfRefM2MExplicit, related_name="rel_to_set") 157 date_added = models.DateTimeField() 158 159 class RelationshipTripleFK(models.Model): 160 first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set_2") 161 second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set_2") 162 third = models.ForeignKey(PersonSelfRefM2M, related_name="too_many_by_far") 163 date_added = models.DateTimeField() 164 165 class RelationshipDoubleFK(models.Model): 166 first = models.ForeignKey(Person, related_name="first_related_name") 167 second = models.ForeignKey(Person, related_name="second_related_name") 168 third = models.ForeignKey(Group, related_name="rel_to_set") 169 date_added = models.DateTimeField() 113 170 114 171 model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute. … … 196 253 invalid_models.missingrelations: 'rel2' has m2m relation with model Rel2, which has not been installed 197 254 invalid_models.missingrelations: 'rel1' has relation with model Rel1, which has not been installed 255 invalid_models.grouptwo: 'primary' has a manually-defined m2m relation through model Membership, which does not have foreign keys to Person and GroupTwo 256 invalid_models.grouptwo: 'secondary' has a manually-defined m2m relation through model MembershipMissingFK, which does not have foreign keys to Group and GroupTwo 257 invalid_models.missingmanualm2mmodel: 'missing_m2m' specifies an m2m relation through model MissingM2MModel, which has not been installed 258 invalid_models.group: The model Group has two manually-defined m2m relations through the model Membership, which is not permitted. Please consider using an extra field on your intermediary model instead. 259 invalid_models.group: Intermediary model RelationshipDoubleFK has more than one foreign key to Person, which is ambiguous and is not permitted. 260 invalid_models.personselfrefm2m: Many-to-many fields with intermediate tables cannot be symmetrical. 261 invalid_models.personselfrefm2m: Intermediary model RelationshipTripleFK has more than two foreign keys to PersonSelfRefM2M, which is ambiguous and is not permitted. 262 invalid_models.personselfrefm2mexplicit: Many-to-many fields with intermediate tables cannot be symmetrical. 198 263 """
