=== modified file 'django/core/serializers/python.py'
|
|
|
88 | 88 | # Handle all other fields |
89 | 89 | else: |
90 | 90 | data[field.name] = field.to_python(field_value) |
91 | | |
| 91 | data["disable_inst_caching"] = True |
92 | 92 | yield base.DeserializedObject(Model(**data), m2m_data) |
93 | 93 | |
94 | 94 | def _get_model(model_identifier): |
=== modified file 'django/core/serializers/xml_serializer.py'
|
|
|
176 | 176 | else: |
177 | 177 | value = field.to_python(getInnerText(field_node).strip()) |
178 | 178 | data[field.name] = value |
179 | | |
| 179 | data["disable_inst_caching"] = True |
180 | 180 | # Return a DeserializedObject so that the m2m data has a place to live. |
181 | 181 | return base.DeserializedObject(Model(**data), m2m_data) |
182 | 182 | |
… |
… |
|
234 | 234 | else: |
235 | 235 | pass |
236 | 236 | return u"".join(inner_text) |
237 | | |
=== modified file 'django/db/models/base.py'
|
|
|
15 | 15 | from django.utils.encoding import smart_str, force_unicode |
16 | 16 | from django.conf import settings |
17 | 17 | from itertools import izip |
| 18 | from weakref import WeakValueDictionary |
18 | 19 | import types |
19 | 20 | import sys |
20 | 21 | import os |
… |
… |
|
77 | 78 | # registered version. |
78 | 79 | return get_model(new_class._meta.app_label, name, False) |
79 | 80 | |
| 81 | def __call__(cls, *args, **kwargs): |
| 82 | if not kwargs.pop('disable_inst_caching', False) and not cls.disable_inst_caching: |
| 83 | key = cls._get_cache_key(args, kwargs) |
| 84 | if key is not None: |
| 85 | obj = cls.__instance_cache__.get(key) |
| 86 | if obj is None: |
| 87 | obj = super(ModelBase, cls).__call__(*args, **kwargs) |
| 88 | cls.__instance_cache__[key] = obj |
| 89 | else: |
| 90 | obj = super(ModelBase, cls).__call__(*args, **kwargs) |
| 91 | else: |
| 92 | obj = super(ModelBase, cls).__call__(*args, **kwargs) |
| 93 | return obj |
| 94 | |
| 95 | |
80 | 96 | class Model(object): |
81 | 97 | __metaclass__ = ModelBase |
82 | 98 | |
… |
… |
|
97 | 113 | def __ne__(self, other): |
98 | 114 | return not self.__eq__(other) |
99 | 115 | |
| 116 | def _get_cache_key(cls, args, kwargs): |
| 117 | # this should be calculated *once*, but isn't atm |
| 118 | pk_position = cls._meta.fields.index(cls._meta.pk) |
| 119 | if len(args) > pk_position: |
| 120 | return args[pk_position] |
| 121 | pk = cls._meta.pk |
| 122 | if pk.name in kwargs: |
| 123 | return kwargs[pk.name] |
| 124 | elif pk.attname in kwargs: |
| 125 | return kwargs[pk.attname] |
| 126 | return None |
| 127 | _get_cache_key = classmethod(_get_cache_key) |
| 128 | |
| 129 | def get_cached_instance(cls, id): |
| 130 | return cls.__instance_cache__.get(id) |
| 131 | get_cached_instance = classmethod(get_cached_instance) |
| 132 | |
100 | 133 | def __init__(self, *args, **kwargs): |
101 | 134 | dispatcher.send(signal=signals.pre_init, sender=self.__class__, args=args, kwargs=kwargs) |
102 | 135 | |
… |
… |
|
197 | 230 | if hasattr(cls, 'get_absolute_url'): |
198 | 231 | cls.get_absolute_url = curry(get_absolute_url, opts, cls.get_absolute_url) |
199 | 232 | |
| 233 | cls.__instance_cache__ = WeakValueDictionary() |
| 234 | cls.disable_inst_caching = getattr(cls, 'disable_inst_caching', False) or not cls._meta.has_auto_field |
| 235 | |
200 | 236 | dispatcher.send(signal=signals.class_prepared, sender=cls) |
201 | 237 | |
202 | 238 | _prepare = classmethod(_prepare) |
… |
… |
|
255 | 291 | setattr(self, self._meta.pk.attname, backend.get_last_insert_id(cursor, self._meta.db_table, self._meta.pk.column)) |
256 | 292 | transaction.commit_unless_managed() |
257 | 293 | |
| 294 | # if we're a new instance that hasn't been written in; save ourself. |
| 295 | if not self.disable_inst_caching: |
| 296 | self.__instance_cache__[self._get_pk_val()] = self |
| 297 | |
258 | 298 | # Run any post-save hooks. |
259 | 299 | dispatcher.send(signal=signals.post_save, sender=self.__class__, instance=self) |
260 | 300 | |
… |
… |
|
315 | 355 | self._collect_sub_objects(seen_objs) |
316 | 356 | |
317 | 357 | # Actually delete the objects |
| 358 | if not self.disable_inst_caching: |
| 359 | pk = self._get_pk_val() |
| 360 | if pk is not None: |
| 361 | self.__instance_cache__.pop(pk, None) |
318 | 362 | delete_objects(seen_objs) |
319 | 363 | |
320 | 364 | delete.alters_data = True |
=== modified file 'django/db/models/fields/related.py'
|
|
|
165 | 165 | if self.field.null: |
166 | 166 | return None |
167 | 167 | raise self.field.rel.to.DoesNotExist |
168 | | other_field = self.field.rel.get_related_field() |
169 | | if other_field.rel: |
170 | | params = {'%s__pk' % self.field.rel.field_name: val} |
171 | | else: |
172 | | params = {'%s__exact' % self.field.rel.field_name: val} |
173 | | rel_obj = self.field.rel.to._default_manager.get(**params) |
| 168 | rel_obj = self.field.rel.to.get_cached_instance(val) |
| 169 | if rel_obj is None: |
| 170 | other_field = self.field.rel.get_related_field() |
| 171 | if other_field.rel: |
| 172 | params = {'%s__pk' % self.field.rel.field_name: val} |
| 173 | else: |
| 174 | params = {'%s__exact' % self.field.rel.field_name: val} |
| 175 | rel_obj = self.field.rel.to._default_manager.get(**params) |
174 | 176 | setattr(instance, cache_name, rel_obj) |
175 | 177 | return rel_obj |
176 | 178 | |
=== modified file 'django/db/models/query.py'
|
|
|
1117 | 1117 | for cls in ordered_classes: |
1118 | 1118 | seen_objs[cls] = seen_objs[cls].items() |
1119 | 1119 | seen_objs[cls].sort() |
| 1120 | clean_inst_cache = cls.__instance_cache__.pop |
1120 | 1121 | |
1121 | 1122 | # Pre notify all instances to be deleted |
1122 | 1123 | for pk_val, instance in seen_objs[cls]: |
1123 | 1124 | dispatcher.send(signal=signals.pre_delete, sender=cls, instance=instance) |
1124 | 1125 | |
1125 | 1126 | pk_list = [pk for pk,instance in seen_objs[cls]] |
| 1127 | # we wipe the cache now; it's *possible* some form of a __get__ lookup may reintroduce an item after |
| 1128 | # the fact with the same pk (extremely unlikely) |
| 1129 | for x in pk_list: |
| 1130 | clean_inst_cache(x, None) |
| 1131 | |
1126 | 1132 | for related in cls._meta.get_all_related_many_to_many_objects(): |
1127 | 1133 | if not isinstance(related.field, generic.GenericRelation): |
1128 | | for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): |
| 1134 | for offset in xrange(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): |
1129 | 1135 | cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \ |
1130 | 1136 | (qn(related.field.m2m_db_table()), |
1131 | 1137 | qn(related.field.m2m_reverse_name()), |
1132 | | ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])), |
1133 | | pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]) |
| 1138 | ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])), |
| 1139 | pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]) |
| 1140 | |
1134 | 1141 | for f in cls._meta.many_to_many: |
1135 | 1142 | if isinstance(f, generic.GenericRelation): |
1136 | 1143 | from django.contrib.contenttypes.models import ContentType |
… |
… |
|
1139 | 1146 | else: |
1140 | 1147 | query_extra = '' |
1141 | 1148 | args_extra = [] |
1142 | | for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): |
| 1149 | for offset in xrange(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): |
1143 | 1150 | cursor.execute(("DELETE FROM %s WHERE %s IN (%s)" % \ |
1144 | 1151 | (qn(f.m2m_db_table()), qn(f.m2m_column_name()), |
1145 | 1152 | ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]]))) + query_extra, |
1146 | 1153 | pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE] + args_extra) |
| 1154 | |
1147 | 1155 | for field in cls._meta.fields: |
1148 | 1156 | if field.rel and field.null and field.rel.to in seen_objs: |
1149 | | for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): |
| 1157 | for offset in xrange(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): |
1150 | 1158 | cursor.execute("UPDATE %s SET %s=NULL WHERE %s IN (%s)" % \ |
1151 | 1159 | (qn(cls._meta.db_table), qn(field.column), qn(cls._meta.pk.column), |
1152 | 1160 | ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])), |
… |
… |
|
1156 | 1164 | for cls in ordered_classes: |
1157 | 1165 | seen_objs[cls].reverse() |
1158 | 1166 | pk_list = [pk for pk,instance in seen_objs[cls]] |
1159 | | for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): |
| 1167 | clean_inst_cache = cls.__instance_cache__.pop |
| 1168 | for x in pk_list: |
| 1169 | clean_inst_cache(x, None) |
| 1170 | for offset in xrange(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE): |
1160 | 1171 | cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \ |
1161 | 1172 | (qn(cls._meta.db_table), qn(cls._meta.pk.column), |
1162 | 1173 | ','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])), |
=== modified file 'tests/modeltests/basic/models.py'
|
|
|
361 | 361 | __test__['API_TESTS'] += """ |
362 | 362 | |
363 | 363 | # You can manually specify the primary key when creating a new object. |
364 | | >>> a101 = Article(id=101, headline='Article 101', pub_date=datetime(2005, 7, 31, 12, 30, 45)) |
| 364 | >>> a101 = Article(id=101, headline=u'Article 101', pub_date=datetime(2005, 7, 31, 12, 30, 45)) |
365 | 365 | >>> a101.save() |
366 | 366 | >>> a101 = Article.objects.get(pk=101) |
367 | 367 | >>> a101.headline |
=== modified file 'tests/modeltests/custom_columns/models.py'
|
|
|
40 | 40 | |
41 | 41 | __test__ = {'API_TESTS':""" |
42 | 42 | # Create a Author. |
43 | | >>> a = Author(first_name='John', last_name='Smith') |
| 43 | >>> a = Author(first_name=u'John', last_name=u'Smith') |
44 | 44 | >>> a.save() |
45 | 45 | |
46 | 46 | >>> a.id |
=== modified file 'tests/modeltests/generic_relations/models.py'
|
|
|
87 | 87 | |
88 | 88 | # Recall that the Mineral class doesn't have an explicit GenericRelation |
89 | 89 | # defined. That's OK, because you can create TaggedItems explicitly. |
90 | | >>> tag1 = TaggedItem(content_object=quartz, tag="shiny") |
91 | | >>> tag2 = TaggedItem(content_object=quartz, tag="clearish") |
| 90 | >>> tag1 = TaggedItem(content_object=quartz, tag=u"shiny") |
| 91 | >>> tag2 = TaggedItem(content_object=quartz, tag=u"clearish") |
92 | 92 | >>> tag1.save() |
93 | 93 | >>> tag2.save() |
94 | 94 | |
=== modified file 'tests/modeltests/many_to_one/models.py'
|
|
|
27 | 27 | |
28 | 28 | __test__ = {'API_TESTS':""" |
29 | 29 | # Create a few Reporters. |
30 | | >>> r = Reporter(first_name='John', last_name='Smith', email='john@example.com') |
| 30 | >>> r = Reporter(first_name=u'John', last_name=u'Smith', email='john@example.com') |
31 | 31 | >>> r.save() |
32 | 32 | |
33 | | >>> r2 = Reporter(first_name='Paul', last_name='Jones', email='paul@example.com') |
| 33 | >>> r2 = Reporter(first_name=u'Paul', last_name=u'Jones', email='paul@example.com') |
34 | 34 | >>> r2.save() |
35 | 35 | |
36 | 36 | # Create an Article. |
=== modified file 'tests/modeltests/select_related/models.py'
|
|
|
107 | 107 | 1 |
108 | 108 | |
109 | 109 | # select_related() also of course applies to entire lists, not just items. |
110 | | # Without select_related() |
| 110 | # Without select_related() (note instance caching still reduces this from 9 to 5) |
111 | 111 | >>> db.reset_queries() |
112 | 112 | >>> world = Species.objects.all() |
113 | 113 | >>> [o.genus.family for o in world] |
114 | 114 | [<Family: Drosophilidae>, <Family: Hominidae>, <Family: Fabaceae>, <Family: Amanitacae>] |
115 | 115 | >>> len(db.connection.queries) |
116 | | 9 |
| 116 | 5 |
117 | 117 | |
118 | 118 | # With select_related(): |
119 | 119 | >>> db.reset_queries() |
… |
… |
|
129 | 129 | >>> pea.genus.family.order.klass.phylum.kingdom.domain |
130 | 130 | <Domain: Eukaryota> |
131 | 131 | |
132 | | # Notice: one few query than above because of depth=1 |
| 132 | # notice: instance caching saves the day; would be 7 without. |
133 | 133 | >>> len(db.connection.queries) |
134 | | 7 |
| 134 | 1 |
135 | 135 | |
136 | 136 | >>> db.reset_queries() |
137 | 137 | >>> pea = Species.objects.select_related(depth=5).get(name="sativum") |
138 | 138 | >>> pea.genus.family.order.klass.phylum.kingdom.domain |
139 | 139 | <Domain: Eukaryota> |
140 | 140 | >>> len(db.connection.queries) |
141 | | 3 |
| 141 | 1 |
142 | 142 | |
143 | 143 | >>> db.reset_queries() |
144 | 144 | >>> world = Species.objects.all().select_related(depth=2) |
145 | 145 | >>> [o.genus.family.order for o in world] |
146 | 146 | [<Order: Diptera>, <Order: Primates>, <Order: Fabales>, <Order: Agaricales>] |
147 | 147 | >>> len(db.connection.queries) |
148 | | 5 |
| 148 | 1 |
149 | 149 | |
150 | 150 | # Reset DEBUG to where we found it. |
151 | 151 | >>> settings.DEBUG = False |