| 1020 | def get_field_by_name(self, opts, filter_name, allow_explicit_fk=True): |
| 1021 | """ |
| 1022 | A wrapper to opts.get_field_by_name, so that 'foo_id' -> 'foo' |
| 1023 | translation does not clutter the main code. TODO: Move to Options. |
| 1024 | """ |
| 1025 | try: |
| 1026 | field, model, direct, m2m = opts.get_field_by_name(filter_name) |
| 1027 | except FieldDoesNotExist: |
| 1028 | for f in opts.fields: |
| 1029 | if allow_explicit_fk and filter_name == f.attname: |
| 1030 | # XXX: A hack to allow foo_id to work in values() for |
| 1031 | # backwards compatibility purposes. If we dropped that |
| 1032 | # feature, this could be removed. |
| 1033 | field, model, direct, m2m = opts.get_field_by_name(f.name) |
| 1034 | break |
| 1035 | else: |
| 1036 | names = opts.get_all_field_names() + self.aggregate_select.keys() |
| 1037 | raise FieldError("Cannot resolve keyword %r into field. " |
| 1038 | "Choices are: %s" % (filter_name, ", ".join(names))) |
| 1039 | return field, model, direct, m2m |
| 1040 | |
| 1041 | def filter_chain_to_relations(self, filter_chain): |
| 1042 | """ |
| 1043 | Turns the passed in filter_chain (for example [related_model, id]) |
| 1044 | into relations and final_field. |
| 1045 | |
| 1046 | The relations will be in the format |
| 1047 | (field, direction) |
| 1048 | Direction itself is either FOLLOW_FORWARD or FOLLOW_REVERSE |
| 1049 | The format is slightly inconvenient in that the direct flag informs if |
| 1050 | the field lives in the related model or in the previous model of the |
| 1051 | join chain. The fields in relations list will always be ForeignKeys, |
| 1052 | OneToOneKeys or GenericForeignKeys(?). |
| 1053 | |
| 1054 | The final field will be the last field in the chain. |
| 1055 | |
| 1056 | Note that one filter_chain part can lead to multiple relations due to |
| 1057 | model inheritance and many_to_many filtering. |
| 1058 | """ |
| 1059 | relations, final_field = [], None |
| 1060 | opts = self.model._meta |
| 1061 | field = None |
| 1062 | for pos, filter_name in enumerate(filter_chain): |
| 1063 | if filter_name == 'pk': |
| 1064 | filter_name = opts.pk.name |
| 1065 | try: |
| 1066 | field, model, direct, m2m = self.get_field_by_name(opts, filter_name) |
| 1067 | except FieldError: |
| 1068 | # We have reached the lookup part of the filter chain |
| 1069 | if field is None: |
| 1070 | # We didn't resolve a single field. |
| 1071 | raise |
| 1072 | return relations, field, filter_chain[pos:] |
| 1073 | if model: |
| 1074 | # The field lives on a base class of the current model. |
| 1075 | # Skip the chain of proxy to the concrete proxied model |
| 1076 | proxied_model = get_proxied_model(opts) |
| 1077 | # Process the internal one-to-one chain into |
| 1078 | # relations |
| 1079 | for int_model in opts.get_base_chain(model): |
| 1080 | if int_model is proxied_model: |
| 1081 | # skip proxy models |
| 1082 | opts = int_model._meta |
| 1083 | continue |
| 1084 | # Note that we do not care that there might be unecessary |
| 1085 | # relations here (stepping through intermediatry relations to get |
| 1086 | # to a field in the base chain, even though the field's value is |
| 1087 | # guaranteed to stay the same throughout the chain). We can prune |
| 1088 | # them later on if necessary. |
| 1089 | o2o_field = opts.get_ancestor_link(int_model) |
| 1090 | relations.append((o2o_field, FOLLOW_FORWARD)) |
| 1091 | opts = int_model._meta |
| 1092 | if m2m and (direct and field.rel.through): |
| 1093 | # For some strange reason GenericRelations pretend to be |
| 1094 | # m2m fields, although they are not. The above field.rel.through |
| 1095 | # check ensures we do not end up here: proper fix - refactor |
| 1096 | # generic relations |
| 1097 | if not direct: |
| 1098 | # This nice little line fetches related model's field |
| 1099 | field = field.field |
| 1100 | through_model = field.rel.through |
| 1101 | through_opts = through_model._meta |
| 1102 | through_field1_name = field.m2m_field_name() |
| 1103 | field1, _, _, _ = through_opts.get_field_by_name(through_field1_name) |
| 1104 | through_field2_name = field.m2m_reverse_field_name() |
| 1105 | field2, _, _, _ = through_opts.get_field_by_name(through_field2_name) |
| 1106 | if direct: |
| 1107 | relations.append((field1, FOLLOW_FORWARD)) |
| 1108 | relations.append((field2, FOLLOW_FORWARD)) |
| 1109 | opts = field.rel.to._meta |
| 1110 | else: |
| 1111 | relations.append((field2, FOLLOW_REVERSE)) |
| 1112 | relations.append((field1, FOLLOW_REVERSE)) |
| 1113 | opts = field.opts |
| 1114 | else: |
| 1115 | if direct and field.rel: |
| 1116 | # Local foreign key |
| 1117 | relations.append((field, FOLLOW_FORWARD)) |
| 1118 | opts = field.rel.to._meta |
| 1119 | elif direct: |
| 1120 | # This is a local field which is not a relation -> the final field. |
| 1121 | return relations, field, filter_chain[pos+1:] |
| 1122 | else: |
| 1123 | # ForeignKey or OneToOneKey defined in the target model. |
| 1124 | field = field.field |
| 1125 | relations.append((field, FOLLOW_REVERSE)) |
| 1126 | opts = field.opts |
| 1127 | |
| 1128 | # We end up in this case when querying 'related_field=val' |
| 1129 | return relations, field, [] |
| 1130 | |
1046 | | raise FieldError("Cannot parse keyword query %r" % arg) |
| 1160 | raise FieldError("Cannot parse keyword query %r" % filter_path) |
| 1161 | |
| 1162 | aggregate_lookup = None |
| 1163 | lookup_type = 'exact' |
| 1164 | for alias, aggregate in self.aggregates.items(): |
| 1165 | if alias == LOOKUP_SEP.join(parts[0:-1]): |
| 1166 | aggregate_lookup = aggregate |
| 1167 | lookup_type = parts[-1] |
| 1168 | # aggregates could support custom lookups, too. |
| 1169 | if lookup_type not in self.query_terms: |
| 1170 | raise Exception('TODO: correct exception type and message') |
| 1171 | break |
| 1172 | if alias == filter_path: |
| 1173 | aggregate_lookup = aggregate |
| 1174 | break |
| 1175 | |
| 1176 | if not aggregate_lookup: |
| 1177 | _ , final_field, lookup_parts = self.filter_chain_to_relations(parts) |
| 1178 | # we could use the final field and lookup_parts for doing custom lookups |
| 1179 | # not done yet. |
| 1180 | if len(lookup_parts) == 1: |
| 1181 | if lookup_parts[0] not in self.query_terms: |
| 1182 | raise FieldError( |
| 1183 | "Join on field %r not permitted. " |
| 1184 | "Did you misspell %r for the lookup type?" |
| 1185 | % (final_field.name, lookup_parts[0]) |
| 1186 | ) |
| 1187 | lookup_type = lookup_parts[0] |
| 1188 | parts = parts[0:-1] |
| 1189 | if len(lookup_parts) > 1: |
| 1190 | if not hasattr(final_field, 'rel'): |
| 1191 | raise FieldError("Join on field %r not permitted." % final_field.name) |
| 1192 | else: |
| 1193 | names = (final_field.rel.to._meta.get_all_field_names() + |
| 1194 | self.aggregate_select.keys()) |
| 1195 | raise FieldError("Cannot resolve keyword %r into field. " |
| 1196 | "Choices are: %s" % (lookup_parts[0], ", ".join(names))) |