Ticket #17030: deferred_init.diff
File deferred_init.diff, 18.4 KB (added by , 13 years ago) |
---|
-
django/db/models/base.py
diff --git a/django/db/models/base.py b/django/db/models/base.py index b5ce39e..daa96d3 100644
a b class Model(object): 287 287 # The reason for the kwargs check is that standard iterator passes in by 288 288 # args, and instantiation for iteration is 33% faster. 289 289 args_len = len(args) 290 if args_len > len(self._meta.fields): 291 # Daft, but matches old exception sans the err msg. 292 raise IndexError("Number of args exceeds number of fields") 293 294 fields_iter = iter(self._meta.fields) 295 if not kwargs: 296 # The ordering of the izip calls matter - izip throws StopIteration 297 # when an iter throws it. So if the first iter throws it, the second 298 # is *not* consumed. We rely on this, so don't change the order 299 # without changing the logic. 300 for val, field in izip(args, fields_iter): 301 setattr(self, field.attname, val) 290 291 # Deferred models have a different set of init fields - this can return 292 # different set of attnames than the attnames of all the fields in the 293 # model. 294 init_attnames = self._meta.get_init_attnames() 295 # This is the common case - the case we have when loading from the DB 296 # Make it fast by special casing. 297 if args_len == len(init_attnames): 298 [setattr(self, attname, val) for val, attname 299 in izip(args, init_attnames)] 302 300 else: 303 # Slower, kwargs-ready version. 304 for val, field in izip(args, fields_iter): 305 setattr(self, field.attname, val) 306 kwargs.pop(field.name, None) 307 # Maintain compatibility with existing calls. 308 if isinstance(field.rel, ManyToOneRel): 309 kwargs.pop(field.attname, None) 310 311 # Now we're left with the unprocessed fields that *must* come from 312 # keywords, or default. 313 314 for field in fields_iter: 315 is_related_object = False 316 # This slightly odd construct is so that we can access any 317 # data-descriptor object (DeferredAttribute) without triggering its 318 # __get__ method. 319 if (field.attname not in kwargs and 320 isinstance(self.__class__.__dict__.get(field.attname), DeferredAttribute)): 321 # This field will be populated on request. 322 continue 323 if kwargs: 324 if isinstance(field.rel, ManyToOneRel): 325 try: 326 # Assume object instance was passed in. 327 rel_obj = kwargs.pop(field.name) 328 is_related_object = True 329 except KeyError: 301 if args_len > len(self._meta.fields): 302 # Daft, but matches old exception sans the err msg. 303 raise IndexError("Number of args exceeds number of fields") 304 305 fields_iter = iter(self._meta.fields) 306 if not kwargs: 307 # The ordering of the izip calls matter - izip throws StopIteration 308 # when an iter throws it. So if the first iter throws it, the second 309 # is *not* consumed. We rely on this, so don't change the order 310 # without changing the logic. 311 for val, field in izip(args, fields_iter): 312 setattr(self, field.attname, val) 313 else: 314 # Slower, kwargs-ready version. 315 for val, field in izip(args, fields_iter): 316 setattr(self, field.attname, val) 317 kwargs.pop(field.name, None) 318 # Maintain compatibility with existing calls. 319 if isinstance(field.rel, ManyToOneRel): 320 kwargs.pop(field.attname, None) 321 322 # Now we're left with the unprocessed fields that *must* come from 323 # keywords, or default. 324 325 for field in fields_iter: 326 is_related_object = False 327 if kwargs: 328 if isinstance(field.rel, ManyToOneRel): 329 try: 330 # Assume object instance was passed in. 331 rel_obj = kwargs.pop(field.name) 332 is_related_object = True 333 except KeyError: 334 try: 335 # Object instance wasn't passed in -- must be an ID. 336 val = kwargs.pop(field.attname) 337 except KeyError: 338 val = field.get_default() 339 else: 340 # Object instance was passed in. Special case: You can 341 # pass in "None" for related objects if it's allowed. 342 if rel_obj is None and field.null: 343 val = None 344 else: 330 345 try: 331 # Object instance wasn't passed in -- must be an ID.332 346 val = kwargs.pop(field.attname) 333 347 except KeyError: 348 # This is done with an exception rather than the 349 # default argument on pop because we don't want 350 # get_default() to be evaluated, and then not used. 351 # Refs #12057. 334 352 val = field.get_default() 335 else:336 # Object instance was passed in. Special case: You can337 # pass in "None" for related objects if it's allowed.338 if rel_obj is None and field.null:339 val = None340 353 else: 341 try: 342 val = kwargs.pop(field.attname) 343 except KeyError: 344 # This is done with an exception rather than the 345 # default argument on pop because we don't want 346 # get_default() to be evaluated, and then not used. 347 # Refs #12057. 348 val = field.get_default() 349 else: 350 val = field.get_default() 351 if is_related_object: 352 # If we are passed a related instance, set it using the 353 # field.name instead of field.attname (e.g. "user" instead of 354 # "user_id") so that the object gets properly cached (and type 355 # checked) by the RelatedObjectDescriptor. 356 setattr(self, field.name, rel_obj) 357 else: 358 setattr(self, field.attname, val) 359 360 if kwargs: 361 for prop in kwargs.keys(): 362 try: 363 if isinstance(getattr(self.__class__, prop), property): 364 setattr(self, prop, kwargs.pop(prop)) 365 except AttributeError: 366 pass 354 val = field.get_default() 355 if is_related_object: 356 # If we are passed a related instance, set it using the 357 # field.name instead of field.attname (e.g. "user" instead of 358 # "user_id") so that the object gets properly cached (and type 359 # checked) by the RelatedObjectDescriptor. 360 setattr(self, field.name, rel_obj) 361 else: 362 setattr(self, field.attname, val) 363 367 364 if kwargs: 368 raise TypeError("'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]) 365 for prop in kwargs.keys(): 366 try: 367 if isinstance(getattr(self.__class__, prop), property): 368 setattr(self, prop, kwargs.pop(prop)) 369 except AttributeError: 370 pass 371 if kwargs: 372 raise TypeError("'%s' is an invalid keyword argument for this function" % kwargs.keys()[0]) 369 373 super(Model, self).__init__() 370 374 signals.post_init.send(sender=self.__class__, instance=self) 371 375 -
django/db/models/loading.py
diff --git a/django/db/models/loading.py b/django/db/models/loading.py index c344686..44ff2f3 100644
a b class AppCache(object): 213 213 self._populate() 214 214 if only_installed and app_label not in self.app_labels: 215 215 return None 216 return self.app_models.get(app_label, SortedDict()).get(model_name.lower())216 return self.app_models.get(app_label, {}).get(model_name.lower()) 217 217 218 218 def register_models(self, app_label, *models): 219 219 """ -
django/db/models/options.py
diff --git a/django/db/models/options.py b/django/db/models/options.py index 0cd52a3..82b7f6a 100644
a b class Options(object): 44 44 self.parents = SortedDict() 45 45 self.duplicate_targets = {} 46 46 self.auto_created = False 47 # Deferred models want to use only a portion of all the fields. 48 # This is a list of fields.attnames we want to load. 49 self.only_load = [] 47 50 48 51 # To handle various inheritance situations, we need to track where 49 52 # managers came from (concrete or abstract base classes). … … class Options(object): 105 108 self.db_table = "%s_%s" % (self.app_label, self.module_name) 106 109 self.db_table = truncate_name(self.db_table, connection.ops.max_name_length()) 107 110 111 112 108 113 def _prepare(self, model): 109 114 if self.order_with_respect_to: 110 115 self.order_with_respect_to = self.get_field(self.order_with_respect_to) … … class Options(object): 164 169 if hasattr(self, '_field_cache'): 165 170 del self._field_cache 166 171 del self._field_name_cache 172 del self._init_attname_cache 167 173 168 174 if hasattr(self, '_name_map'): 169 175 del self._name_map … … class Options(object): 231 237 self._fill_fields_cache() 232 238 return self._field_cache 233 239 240 def get_init_attnames(self): 241 """ 242 Returns a sequence of attribute names for model initialization. Note 243 that for deferred models this list contains just the loaded field 244 attribute names, not all of the model's attnames. 245 """ 246 try: 247 self._init_attname_cache 248 except AttributeError: 249 self._fill_fields_cache() 250 return self._init_attname_cache 251 234 252 def _fill_fields_cache(self): 235 253 cache = [] 236 254 for parent in self.parents: … … class Options(object): 242 260 cache.extend([(f, None) for f in self.local_fields]) 243 261 self._field_cache = tuple(cache) 244 262 self._field_name_cache = [x for x, _ in cache] 263 if self.only_load: 264 self._init_attname_cache = tuple( 265 [x.attname for x, _ in cache 266 if x.attname in self.only_load] 267 ) 268 else: 269 self._init_attname_cache = tuple([x.attname for x, _ in cache]) 270 271 def set_loaded_fields(self, defer): 272 """ 273 Deferred model class creation will call this method. This will set 274 the deferred_fields list and then delete the _init_attname_cache. 275 Next access to get_init_fields() will reload that cache. 276 """ 277 # Due to some strange things in select_related query.py iterator 278 # we can be called with a list of defer fields which can be either 279 # attnames or or field names. TODO: Fix this (in query.py) 280 self.only_load = [f.attname for f in self.fields 281 if f.name not in defer and f.attname not in defer] 282 del self._init_attname_cache 245 283 246 284 def _many_to_many(self): 247 285 try: -
django/db/models/query.py
diff --git a/django/db/models/query.py b/django/db/models/query.py index be42d02..83dedd4 100644
a b class QuerySet(object): 265 265 index_start = len(extra_select) 266 266 aggregate_start = index_start + len(load_fields or self.model._meta.fields) 267 267 268 skip = None269 268 if load_fields and not fill_cache: 270 269 # Some fields have been deferred, so we have to initialise 271 270 # via keyword arguments. 272 271 skip = set() 273 init_list = []274 272 for field in fields: 275 273 if field.name not in load_fields: 276 274 skip.add(field.attname) 277 else:278 init_list.append(field.attname)279 275 model_cls = deferred_class_factory(self.model, skip) 280 276 else: 277 model_cls = self.model 281 278 # Cache db and model outside the loop 282 279 db = self.db 283 model = self.model284 280 compiler = self.query.get_compiler(using=db) 285 281 if fill_cache: 286 klass_info = get_klass_info( model, max_depth=max_depth,282 klass_info = get_klass_info(self.model, max_depth=max_depth, 287 283 requested=requested, only_load=only_load) 288 284 for row in compiler.results_iter(): 289 285 if fill_cache: 290 286 obj, _ = get_cached_row(row, index_start, db, klass_info, 291 287 offset=len(aggregate_select)) 292 288 else: 293 if skip: 294 row_data = row[index_start:aggregate_start] 295 obj = model_cls(**dict(zip(init_list, row_data))) 296 else: 297 # Omit aggregates in object creation. 298 obj = model(*row[index_start:aggregate_start]) 289 # Omit aggregates in object creation. 290 obj = model_cls(*row[index_start:aggregate_start]) 299 291 300 292 # Store the source database of the object 301 293 obj._state.db = db … … def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None, 1257 1249 else: 1258 1250 load_fields = None 1259 1251 1252 # TODO - Due to removal of special handling of deferred model's __init__ 1253 # we could probably do some cleanup here. 1260 1254 if load_fields: 1261 1255 # Handle deferred fields. 1262 1256 skip = set() … … def get_cached_row(row, index_start, using, klass_info, offset=0): 1345 1339 if fields == (None,) * field_count: 1346 1340 obj = None 1347 1341 else: 1348 if field_names: 1349 obj = klass(**dict(zip(field_names, fields))) 1350 else: 1351 obj = klass(*fields) 1342 obj = klass(*fields) 1352 1343 1353 1344 # If an object was retrieved, set the database state. 1354 1345 if obj: … … class RawQuerySet(object): 1461 1452 model_cls = deferred_class_factory(self.model, skip) 1462 1453 else: 1463 1454 model_cls = self.model 1464 # All model's fields are present in the query. So, it is possible 1465 # to use *args based model instantation. For each field of the model, 1466 # record the query column position matching that field. 1467 model_init_field_pos = [] 1468 for field in self.model._meta.fields: 1469 model_init_field_pos.append(model_init_field_names[field.attname]) 1455 # For each field of the model, record the query column position matching 1456 # that field. Note that we must use the get_init_attnames() method of 1457 # the above fetched model_cls, because if it is a deferred model class 1458 # its __init__ will expect the field in get_init_attnames order. 1459 model_init_field_pos = [] 1460 for attname in model_cls._meta.get_init_attnames(): 1461 model_init_field_pos.append(model_init_field_names[attname]) 1470 1462 if need_resolv_columns: 1471 1463 fields = [self.model_fields.get(c, None) for c in self.columns] 1472 1464 # Begin looping through the query values. … … class RawQuerySet(object): 1474 1466 if need_resolv_columns: 1475 1467 values = compiler.resolve_columns(values, fields) 1476 1468 # Associate fields to values 1477 if skip: 1478 model_init_kwargs = {} 1479 for attname, pos in model_init_field_names.iteritems(): 1480 model_init_kwargs[attname] = values[pos] 1481 instance = model_cls(**model_init_kwargs) 1482 else: 1483 model_init_args = [values[pos] for pos in model_init_field_pos] 1484 instance = model_cls(*model_init_args) 1469 model_init_args = [values[pos] for pos in model_init_field_pos] 1470 instance = model_cls(*model_init_args) 1485 1471 if annotation_fields: 1486 1472 for column, pos in annotation_fields: 1487 1473 setattr(instance, column, values[pos]) -
django/db/models/query_utils.py
diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py index a56ab5c..a3299eb 100644
a b circular import difficulties. 9 9 import weakref 10 10 11 11 from django.db.backends import util 12 from django.db.models.loading import get_model 12 13 from django.utils import tree 13 14 14 15 … … def deferred_class_factory(model, attrs): 146 147 being replaced with DeferredAttribute objects. The "pk_value" ties the 147 148 deferred attributes to a particular instance of the model. 148 149 """ 149 class Meta:150 proxy = True151 app_label = model._meta.app_label152 153 150 # The app_cache wants a unique name for each model, otherwise the new class 154 151 # won't be created (we get an old one back). Therefore, we generate the 155 152 # name using the passed in attrs. It's OK to reuse an existing class 156 153 # object if the attrs are identical. 157 154 name = "%s_Deferred_%s" % (model.__name__, '_'.join(sorted(list(attrs)))) 158 155 name = util.truncate_name(name, 80, 32) 156 deferred_model = get_model(model._meta.app_label, name) 157 if deferred_model: 158 return deferred_model 159 160 class Meta: 161 proxy = True 162 app_label = model._meta.app_label 163 159 164 160 165 overrides = dict([(attr, DeferredAttribute(attr, model)) 161 166 for attr in attrs]) 162 167 overrides["Meta"] = Meta 163 168 overrides["__module__"] = model.__module__ 164 169 overrides["_deferred"] = True 165 return type(name, (model,), overrides) 170 deferred_model = type(name, (model,), overrides) 171 deferred_model._meta.set_loaded_fields(attrs) 172 return deferred_model 166 173 167 174 # The above function is also used to unpickle model instances with deferred 168 175 # fields.