Ticket #5420: lazy.diff
File lazy.diff, 48.8 KB (added by , 16 years ago) |
---|
-
django/db/models/sql/query.py
50 50 self.join_map = {} 51 51 self.rev_join_map = {} # Reverse of join_map. 52 52 self.quote_cache = {} 53 self.default_cols = True54 53 self.default_ordering = True 55 54 self.standard_ordering = True 56 55 self.ordering_aliases = [] … … 73 72 self.distinct = False 74 73 self.select_related = False 75 74 self.related_select_cols = [] 75 self.custom_fields = False 76 76 77 77 # Arbitrary maximum limit for select_related. Prevents infinite 78 78 # recursion. Can be changed by the depth parameter to select_related(). … … 162 162 obj.join_map = self.join_map.copy() 163 163 obj.rev_join_map = self.rev_join_map.copy() 164 164 obj.quote_cache = {} 165 obj.default_cols = self.default_cols166 165 obj.default_ordering = self.default_ordering 167 166 obj.standard_ordering = self.standard_ordering 168 167 obj.ordering_aliases = [] … … 181 180 obj.distinct = self.distinct 182 181 obj.select_related = self.select_related 183 182 obj.related_select_cols = [] 183 obj.custom_fields = self.custom_fields 184 184 obj.max_depth = self.max_depth 185 185 obj.extra_select = self.extra_select.copy() 186 186 obj.extra_tables = self.extra_tables … … 197 197 obj._setup_query() 198 198 return obj 199 199 200 def get_base_fields(self): 201 """ 202 Returns a list of fields on the base object (i.e., not related objects) 203 that are retrieved by this query. In the result row, these columns are 204 row[len(self.extra_select):len(self.extra_select)+len(self.get_base_fields())] 205 """ 206 if self.select_fields: 207 return self.select_fields 208 else: 209 return self.model._meta.default_select_fields 210 200 211 def results_iter(self): 201 212 """ 202 213 Returns an iterator over the results from executing this query. … … 210 221 # We only set this up here because 211 222 # related_select_fields isn't populated until 212 223 # execute_sql() has been called. 213 if self.select_fields: 214 fields = self.select_fields + self.related_select_fields 215 else: 216 fields = self.model._meta.fields 224 fields = self.get_base_fields() 225 if self.related_select_fields: 226 fields = fields + self.related_select_fields 217 227 row = self.resolve_columns(row, fields) 218 228 yield row 219 229 … … 422 432 423 433 If 'with_aliases' is true, any column names that are duplicated 424 434 (without the table names) are given unique aliases. This is needed in 425 some cases to avoid ambiguitity with nested queries. 435 some cases to avoid ambiguitity with nested queries. 426 436 """ 427 437 qn = self.quote_name_unless_alias 428 438 qn2 = self.connection.ops.quote_name … … 432 442 col_aliases = aliases.copy() 433 443 else: 434 444 col_aliases = set() 445 435 446 if self.select: 436 447 for col in self.select: 437 448 if isinstance(col, (list, tuple)): 438 r = '%s.%s' % (qn(col[0]), qn(col[1])) 439 if with_aliases and col[1] in col_aliases: 440 c_alias = 'Col%d' % len(col_aliases) 441 result.append('%s AS %s' % (r, c_alias)) 442 aliases.add(c_alias) 443 col_aliases.add(c_alias) 444 else: 445 result.append(r) 446 aliases.add(r) 447 col_aliases.add(col[1]) 449 result.append(self._get_column( 450 table_alias=col[0],field=col[1], 451 with_aliases=with_aliases, 452 col_aliases=col_aliases, 453 aliases=aliases)) 448 454 else: 449 455 result.append(col.as_sql(quote_func=qn)) 450 456 if hasattr(col, 'alias'): 451 457 aliases.add(col.alias) 452 458 col_aliases.add(col.alias) 453 elif self.default_cols: 454 cols, new_aliases = self.get_default_columns(with_aliases, 455 col_aliases) 456 result.extend(cols) 457 aliases.update(new_aliases) 458 for table, col in self.related_select_cols: 459 r = '%s.%s' % (qn(table), qn(col)) 460 if with_aliases and col in col_aliases: 461 c_alias = 'Col%d' % len(col_aliases) 462 result.append('%s AS %s' % (r, c_alias)) 463 aliases.add(c_alias) 464 col_aliases.add(c_alias) 465 else: 466 result.append(r) 467 aliases.add(r) 468 col_aliases.add(col) 459 else: 460 result.extend(self.get_base_columns(with_aliases, col_aliases, aliases)) 461 462 for table, field in self.related_select_cols: 463 result.append(self._get_column(table, field, 464 with_aliases, col_aliases, aliases)) 469 465 470 466 self._select_aliases = aliases 471 467 return result 472 468 473 def get_default_columns(self, with_aliases=False, col_aliases=None, 474 start_alias=None, opts=None, as_pairs=False): 469 def _get_table_alias(self, model, seen, root_pk): 475 470 """ 476 Computes the default columns for selecting every field in the base 477 model. 471 Gets a table alias for the given model. Seen is a dict 472 mapping already-used models to table aliases, and may be updated 473 as a side effect. 474 """ 475 try: 476 return seen[model] 477 except KeyError: 478 alias = self.join((seen[None], model._meta.db_table, 479 root_pk, model._meta.pk.column)) 480 seen[model] = alias 481 return alias 482 483 def get_base_columns(self, with_aliases, col_aliases, aliases): 484 """ 485 Computes the columns to select on the base model. By default, this includes all 486 non-lazy fields. However, this list can be modified by calling toggle_fields. 478 487 479 Returns a list of strings, quoted appropriately for use in SQL 480 directly, as well as a set of aliases used in the select statement (if 481 'as_pairs' is True, returns a list of (alias, col_name) pairs instead 482 of strings as the first component and None as the second component). 483 """ 484 result = [] 485 if opts is None: 486 opts = self.model._meta 487 if start_alias: 488 table_alias = start_alias 489 else: 490 table_alias = self.tables[0] 488 Returns a list of strings, quoted appropriately for use in SQL directly. 489 As a side effect, updates the aliases and col_aliases sets passed in as an argument. 490 """ 491 result = [] 492 opts = self.model._meta 491 493 root_pk = opts.pk.column 492 seen = {None: table_alias} 493 qn = self.quote_name_unless_alias 494 qn2 = self.connection.ops.quote_name 494 seen = {None: self.tables[0]} 495 495 aliases = set() 496 for field, model in opts.get_fields_with_model(): 497 try: 498 alias = seen[model] 499 except KeyError: 500 alias = self.join((table_alias, model._meta.db_table, 501 root_pk, model._meta.pk.column)) 502 seen[model] = alias 503 if as_pairs: 504 result.append((alias, field.column)) 505 continue 506 if with_aliases and field.column in col_aliases: 507 c_alias = 'Col%d' % len(col_aliases) 508 result.append('%s.%s AS %s' % (qn(alias), 509 qn2(field.column), c_alias)) 510 col_aliases.add(c_alias) 511 aliases.add(c_alias) 512 else: 513 r = '%s.%s' % (qn(alias), qn2(field.column)) 514 result.append(r) 515 aliases.add(r) 516 if with_aliases: 517 col_aliases.add(field.column) 518 if as_pairs: 519 return result, None 520 return result, aliases 496 497 if self.custom_fields: 498 fields_with_model = [(f, opts.get_field_by_name(f.name)[1]) for f in self.select_fields] 499 else: 500 fields_with_model = opts.get_default_select_fields_with_model() 501 502 for field, model in fields_with_model: 503 table_alias = self._get_table_alias(model, seen, root_pk) 504 result.append(self._get_column(table_alias, field, 505 with_aliases, col_aliases, aliases)) 506 507 return result 508 509 def get_related_columns(self, start_alias, opts): 510 """ 511 Computes the default columns for selecting every non-lazy field for a related 512 model (described by opts). The related field columns currently cannot be 513 customized per-query. 521 514 515 Returns a list of (alias, field) pairs, where alias is a string quoted 516 appropriately for use in SQL directly. 517 """ 518 result = [] 519 seen = {None: start_alias} 520 root_pk = opts.pk.column 521 522 for field, model in opts.get_default_select_fields_with_model(): 523 result.append((self._get_table_alias(model, seen, root_pk), field)) 524 525 return result 526 527 def _get_field_select(self, table_alias, field): 528 """ 529 Returns a string with a SQL column name, quoted appropriately for use 530 in a SQL select statement. 531 """ 532 return '%s.%s' % (self.quote_name_unless_alias(table_alias), 533 self.connection.ops.quote_name(field.column)) 534 535 536 def _get_column(self, table_alias, field, with_aliases, col_aliases, aliases): 537 """ 538 Returns a string for a SQL select statement, containing a column name 539 as well as a column alias if necessary. 540 541 The col_aliases and aliases sets are modified as a side effect. 542 """ 543 r = self._get_field_select(table_alias, field) 544 545 if with_aliases and column_name in col_aliases: 546 c_alias = 'Col%d' % len(col_aliases) 547 548 col_aliases.add(c_alias) 549 aliases.add(c_alias) 550 551 return '%s AS %s' % (r, c_alias) 552 else: 553 aliases.add(r) 554 if with_aliases: 555 col_aliases.add(column_name) 556 557 return r 558 522 559 def get_from_clause(self): 523 560 """ 524 561 Returns a list of strings that are joined together to go after the … … 873 910 self.tables[pos] = new_alias 874 911 self.change_aliases(change_map) 875 912 913 def toggle_fields(self, fetch_only, lazy, fetch): 914 915 self.custom_fields = True 916 917 opts = self.model._meta 918 919 if fetch_only is not None: 920 self.select_fields = [] 921 922 if opts.pk.attname not in fetch_only: 923 self.select_fields.append(opts.pk) 924 925 for field_name in fetch_only: 926 self.select_fields.append(opts.get_field(field_name)) 927 928 if not self.select_fields: 929 self.select_fields = opts.default_select_fields[:] 930 931 if fetch is not None: 932 for field_name in fetch: 933 self.select_fields.append(opts.get_field(field_name)) 934 935 if lazy is not None: 936 self.select_fields = [f for f in self.select_fields if f.name not in lazy] 937 876 938 def get_initial_alias(self): 877 939 """ 878 940 Returns the first alias for this query, after increasing its reference … … 1046 1108 f.rel.get_related_field().column), 1047 1109 exclusions=used.union(avoid), promote=promote) 1048 1110 used.add(alias) 1049 self.related_select_cols.extend(self.get_default_columns(1050 start_alias=alias, opts=f.rel.to._meta, as_pairs=True)[0])1051 self.related_select_fields.extend(f.rel.to._meta. fields)1111 new_related_cols = self.get_related_columns(start_alias=alias, opts=f.rel.to._meta) 1112 self.related_select_cols.extend(new_related_cols) 1113 self.related_select_fields.extend(f.rel.to._meta.default_select_fields) 1052 1114 if restricted: 1053 1115 next = requested.get(f.name, {}) 1054 1116 else: … … 1285 1347 dupe_set = set() 1286 1348 exclusions = set() 1287 1349 extra_filters = [] 1350 last_pos = len(names) - 1 1351 1288 1352 for pos, name in enumerate(names): 1289 1353 try: 1290 1354 exclusions.add(int_alias) … … 1373 1437 dupe_multis, exclusions, nullable=True, 1374 1438 reuse=can_reuse) 1375 1439 joins.extend([int_alias, alias]) 1376 elif field.rel: 1377 # One-to-one or many-to-one field 1440 if not field.rel or pos == last_pos: 1441 # Not a one-to-one or many-to-one field. 1442 # 1443 # Except, we do include one-to-one or many-to-one fields if their name is at the 1444 # end of the list.E.g., if names = ["fk_id"], we don't want to do any joins because 1445 # fk_id is stored in that row directly. 1446 1447 target = field 1448 break 1449 else: 1450 # One-to-one or many-to-one field, except as described above. 1378 1451 if cached_data: 1379 1452 (table, from_col, to_col, opts, target) = cached_data 1380 1453 else: … … 1389 1462 alias = self.join((alias, table, from_col, to_col), 1390 1463 exclusions=exclusions, nullable=field.null) 1391 1464 joins.append(alias) 1392 else:1393 # Non-relation fields.1394 target = field1395 break1396 1465 else: 1397 1466 orig_field = field 1398 1467 field = field.field … … 1528 1597 name.split(LOOKUP_SEP), opts, alias, False, allow_m2m, 1529 1598 True) 1530 1599 final_alias = joins[-1] 1531 col = target.column1532 if len(joins) > 1:1533 join = self.alias_map[final_alias]1534 if col == join[RHS_JOIN_COL]:1535 self.unref_alias(final_alias)1536 final_alias = join[LHS_ALIAS]1537 col = join[LHS_JOIN_COL]1538 joins = joins[:-1]1539 1600 self.promote_alias_chain(joins[1:]) 1540 self.select.append((final_alias, col))1601 self.select.append((final_alias, target)) 1541 1602 self.select_fields.append(field) 1542 1603 except MultiJoin: 1543 1604 raise FieldError("Invalid field name: '%s'" % name) -
django/db/models/base.py
10 10 11 11 import django.db.models.manager # Imported to register signal handler. 12 12 from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError 13 from django.db.models.fields import AutoField 13 from django.db.models.fields import AutoField, LazyDescriptor 14 14 from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField 15 15 from django.db.models.query import delete_objects, Q, CollectedObjects 16 16 from django.db.models.options import Options … … 28 28 """ 29 29 def __new__(cls, name, bases, attrs): 30 30 super_new = super(ModelBase, cls).__new__ 31 32 # 33 # The LazyModel subclass passes _lazy_fields in attrs. In this case we quickly 34 # create a new Model subclass which is identical to its parent class 35 # (sharing e.g. the same _meta object), except for its _lazy_fields set. 36 # This allows certain fields of the Model to be lazily-loaded 37 # when they are first used, and only saved to the database if they're changed. 38 # 39 lazy_fields = attrs.get('_lazy_fields', None) 40 if lazy_fields is not None: 41 return super_new(cls, bases[0].__name__, bases, attrs) 42 31 43 parents = [b for b in bases if isinstance(b, ModelBase)] 32 44 if not parents: 33 45 # If this isn't a subclass of Model, don't do anything special. … … 70 82 if getattr(new_class, '_default_manager', None): 71 83 new_class._default_manager = None 72 84 85 # make sure subclasses don't share _lazy_fields sets 86 new_class._lazy_fields = None 87 73 88 # Bail out early if we have already created this class. 74 89 m = get_model(new_class._meta.app_label, name, False) 75 90 if m is not None: … … 225 240 226 241 for field in fields_iter: 227 242 rel_obj = None 243 use_default = False 244 228 245 if kwargs: 229 246 if isinstance(field.rel, ManyToOneRel): 230 247 try: … … 235 252 # Object instance wasn't passed in -- must be an ID. 236 253 val = kwargs.pop(field.attname) 237 254 except KeyError: 238 val = field.get_default()255 use_default = True 239 256 else: 240 257 # Object instance was passed in. Special case: You can 241 258 # pass in "None" for related objects if it's allowed. 242 259 if rel_obj is None and field.null: 243 260 val = None 244 261 else: 245 val = kwargs.pop(field.attname, field.get_default()) 262 try: 263 val = kwargs.pop(field.attname) 264 except KeyError: 265 use_default = True 246 266 else: 247 val = field.get_default() 267 use_default = True 268 269 if use_default: 270 if self._lazy_fields is not None and (field.attname in self._lazy_fields): 271 continue 272 273 val = field.get_default() 274 248 275 # If we got passed a related instance, set it using the field.name 249 276 # instead of field.attname (e.g. "user" instead of "user_id") so 250 277 # that the object gets properly cached (and type checked) by the … … 264 291 if kwargs: 265 292 raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0] 266 293 signals.post_init.send(sender=self.__class__, instance=self) 294 295 @classmethod 296 def _new_from_select_values(cls, vals, fields): 297 """ 298 Creates a new instance of this model class. 299 The class will be instantiated as if calling __init__ with kwargs 300 fields[i]=vals[i] for all i (i.e., fields and vals are parallel lists). 301 302 However, it may call __init__ using positional arguments when all 303 fields are provided (i.e., there are no lazy fields). 304 """ 305 306 # we use pointer-equality to make the test faster in the normal case 307 all_fields = fields is cls._meta.fields 308 309 if all_fields: 310 obj = cls(*vals) # the comments for __init__ indicate this is faster 311 else: 312 obj = cls(**dict(zip([f.attname for f in fields], vals))) 267 313 314 # 315 # We may have set a default-lazy field in the constructor; if so, we 316 # clear its dirty bit since that value came straight from a SELECT. 317 # However, we optimize the case where there are no lazy fields, to avoid 318 # making object instantiation slower in the normal case. 319 # 320 if cls._lazy_fields is not None: 321 for field in cls._meta.fields: 322 field.mark_unchanged(obj) 323 324 return obj 325 326 @classmethod 327 def _get_lazy_subclass(cls, non_lazy_fields): 328 """ 329 Returns a subclass which is identical to the given class, except that 330 every field not in non_lazy_fields becomes lazy. However, any field 331 that was already lazy in the given class stays lazy in the subclass. 332 """ 333 if non_lazy_fields is cls._meta.fields: 334 return cls 335 336 non_lazy_names = set() 337 for field in non_lazy_fields: 338 non_lazy_names.add(field.name) 339 340 new_lazy_fields = [f for f in cls._meta.fields if f.name not in non_lazy_names 341 and not cls.is_lazy_field(f)] 342 343 if new_lazy_fields: 344 345 # Any field which is lazy in the base class remains lazy in the 346 # subclass, even if it's in non_lazy_fields. The reasoning for this 347 # is that default-lazy fields likely have a lot of data, so we 348 # probably still want to check if it's dirty when we call save(). 349 350 lazy_fields = set([f.attname for f in new_lazy_fields]) 351 if cls._lazy_fields is not None: 352 lazy_fields.update(cls._lazy_fields) 353 354 class LazyModel(cls): 355 _lazy_fields = lazy_fields 356 357 for field in new_lazy_fields: 358 setattr(LazyModel, field.attname, LazyDescriptor(field)) 359 360 return LazyModel 361 else: 362 return cls 363 364 @classmethod 365 def is_lazy_field(cls, field): 366 return cls._lazy_fields is not None and (field.attname in cls._lazy_fields) 367 268 368 def __repr__(self): 269 369 return smart_str(u'<%s: %s>' % (self.__class__.__name__, unicode(self))) 270 370 … … 341 441 self.save_base(raw, parent) 342 442 setattr(self, field.attname, self._get_pk_val(parent._meta)) 343 443 344 non_pks = [f for f in meta.local_fields if not f.primary_key]444 non_pks = [f for f in meta.local_fields if (not f.primary_key) and f.write_on_save(self)] 345 445 346 446 # First, try an UPDATE. If that doesn't update anything, do an INSERT. 347 447 pk_val = self._get_pk_val(meta) … … 364 464 if not pk_set: 365 465 if force_update: 366 466 raise ValueError("Cannot force an update in save() with no primary key.") 367 values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields if not isinstance(f, AutoField) ]467 values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields if not isinstance(f, AutoField) and f.write_on_save(self)] 368 468 else: 369 values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields ]469 values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields if f.write_on_save(self)] 370 470 371 471 if meta.order_with_respect_to: 372 472 field = meta.order_with_respect_to … … 384 484 if update_pk: 385 485 setattr(self, meta.pk.attname, result) 386 486 transaction.commit_unless_managed() 487 488 for f in non_pks: 489 f.mark_unchanged(self) 387 490 388 491 if signal: 389 492 signals.post_save.send(sender=self.__class__, instance=self, … … 405 508 return 406 509 407 510 for related in self._meta.get_all_related_objects(): 511 if related.opts.is_view: 512 continue 513 408 514 rel_opts_name = related.get_accessor_name() 409 515 if isinstance(related.field.rel, OneToOneRel): 410 516 try: -
django/db/models/manager.py
134 134 def reverse(self, *args, **kwargs): 135 135 return self.get_query_set().reverse(*args, **kwargs) 136 136 137 def toggle_fields(self, *args, **kwargs): 138 return self.get_query_set().toggle_fields(*args, **kwargs) 139 137 140 def _insert(self, values, **kwargs): 138 141 return insert_query(self.model, values, **kwargs) 139 142 -
django/db/models/options.py
42 42 self.pk = None 43 43 self.has_auto_field, self.auto_field = False, None 44 44 self.one_to_one_field = None 45 self.abstract = False 45 self.has_lazy_field = False 46 self.abstract = False 46 47 self.parents = SortedDict() 47 48 self.duplicate_targets = {} 48 49 # Managers that have been inherited from abstract base classes. These … … 156 157 if hasattr(self, '_name_map'): 157 158 del self._name_map 158 159 160 if field.lazy: 161 self.has_lazy_field = True 162 159 163 def add_virtual_field(self, field): 160 164 self.virtual_fields.append(field) 161 165 … … 198 202 return self._field_name_cache 199 203 fields = property(_fields) 200 204 205 def _default_select_fields(self): 206 try: 207 return self._default_field_name_cache 208 except AttributeError: 209 self._fill_fields_cache() 210 return self._default_field_name_cache 211 212 default_select_fields = property(_default_select_fields) 213 214 def get_default_select_fields_with_model(self): 215 """ 216 Returns a sequence of (field, model) pairs for only those fields which 217 are fetched by default on a SELECT query. The "model" 218 element is None for fields on the current model. Mostly of use when 219 constructing queries so that we know which model a field belongs to. 220 """ 221 try: 222 self._default_field_cache 223 except AttributeError: 224 self._fill_fields_cache() 225 return self._default_field_cache 226 201 227 def get_fields_with_model(self): 202 228 """ 203 229 Returns a sequence of (field, model) pairs for all fields. The "model" … … 221 247 cache.extend([(f, None) for f in self.local_fields]) 222 248 self._field_cache = tuple(cache) 223 249 self._field_name_cache = [x for x, _ in cache] 250 251 if self.has_lazy_field: 252 self._default_field_cache = tuple([(field, model) for field, model in cache if not field.lazy]) 253 self._default_field_name_cache = [x for x, _ in self._default_field_cache] 254 else: 255 self._default_field_cache = self._field_cache 256 self._default_field_name_cache = self._field_name_cache 257 224 258 225 259 def _many_to_many(self): 226 260 try: -
django/db/models/fields/__init__.py
64 64 db_index=False, rel=None, default=NOT_PROVIDED, editable=True, 65 65 serialize=True, unique_for_date=None, unique_for_month=None, 66 66 unique_for_year=None, choices=None, help_text='', db_column=None, 67 db_tablespace=None, auto_created=False ):67 db_tablespace=None, auto_created=False, lazy=False): 68 68 self.name = name 69 69 self.verbose_name = verbose_name 70 70 self.primary_key = primary_key … … 85 85 self.db_column = db_column 86 86 self.db_tablespace = db_tablespace or settings.DEFAULT_INDEX_TABLESPACE 87 87 self.auto_created = auto_created 88 self.lazy = lazy 88 89 89 90 # Set db_index to True if the field has a relationship and doesn't explicitly set db_index. 90 91 self.db_index = db_index … … 97 98 self.creation_counter = Field.creation_counter 98 99 Field.creation_counter += 1 99 100 101 def write_on_save(self, instance): 102 return not instance.is_lazy_field(self) or getattr(instance, self.get_changed_name(), False) 103 104 def mark_unchanged(self, instance): 105 if instance.is_lazy_field(self): 106 setattr(instance, self.get_changed_name(), False) 107 100 108 def __cmp__(self, other): 101 109 # This is needed because bisect does not take a comparison function. 102 110 return cmp(self.creation_counter, other.creation_counter) … … 160 168 if self.choices: 161 169 setattr(cls, 'get_%s_display' % self.name, curry(cls._get_FIELD_display, field=self)) 162 170 171 if self.lazy: 172 if cls._lazy_fields is None: 173 cls._lazy_fields = set() 174 cls._lazy_fields.add(self.attname) 175 setattr(cls, self.attname, LazyDescriptor(self)) 176 163 177 def get_attname(self): 164 178 return self.name 165 179 … … 171 185 def get_cache_name(self): 172 186 return '_%s_cache' % self.name 173 187 188 def get_changed_name(self): 189 return '_%s_changed' % self.attname 190 174 191 def get_internal_type(self): 175 192 return self.__class__.__name__ 176 193 … … 870 887 def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs): 871 888 self.schema_path = schema_path 872 889 Field.__init__(self, verbose_name, name, **kwargs) 890 891 class LazyDescriptor(object): 892 """ 893 A descriptor for a field that is lazy-loaded from the database the first time it is accessed. 894 Useful for large fields that are infrequently accessed by Django. 895 """ 896 897 def __init__(self, lazy_field): 898 self.field = lazy_field 899 900 # we use a different cache_name than field.get_cache_name() to allow lazy foreign key fields 901 self.cache_name = '_%s_lcache' % lazy_field.attname 902 903 if self.field.primary_key: 904 raise TypeError, "A model's primary key field cannot be lazy." 905 906 def __get__(self, instance, instance_type=None): 907 try: 908 return getattr(instance, self.cache_name) 909 except AttributeError: 910 911 pkVal = instance._get_pk_val() 912 if pkVal is None: 913 val = self.field.get_default() 914 else: 915 results = instance.__class__._default_manager.filter(pk=pkVal).values(self.field.attname) 916 val = results[0][self.field.attname] # could be error if deleted 917 918 setattr(instance, self.cache_name, val) 919 return val 920 921 def __set__(self, instance, value): 922 setattr(instance, self.cache_name, value) 923 setattr(instance, self.field.get_changed_name(), True) -
django/db/models/query.py
272 272 max_depth = self.query.max_depth 273 273 extra_select = self.query.extra_select.keys() 274 274 index_start = len(extra_select) 275 276 fields = self.query.get_base_fields() 277 # Get a subclass which knows which fields are lazy and which are not. 278 # In the case where all fields are non-lazy, this is just self.model. 279 lazy_model = self.model._get_lazy_subclass(fields) 280 275 281 for row in self.query.results_iter(): 276 282 if fill_cache: 277 obj, _ = get_cached_row( self.model, row, index_start,278 max_depth, requested=requested )283 obj, _ = get_cached_row(lazy_model, row, index_start, 284 max_depth, requested=requested, fields=fields) 279 285 else: 280 obj = self.model(*row[index_start:])286 obj = lazy_model._new_from_select_values(row[index_start:], fields) 281 287 for i, k in enumerate(extra_select): 282 288 setattr(obj, k, row[i]) 283 289 yield obj 284 290 291 def toggle_fields(self, fetch_only=None, lazy=None, fetch=None): 292 """ 293 Change the fields that are retrieved by this query. 294 """ 295 296 assert self.query.can_filter(), "Cannot change a query once a slice has been taken" 297 clone = self._clone() 298 clone.query.toggle_fields(fetch_only, lazy, fetch) 299 return clone 300 285 301 def count(self): 286 302 """ 287 303 Performs a SELECT COUNT() and returns the number of records as an … … 679 695 else: 680 696 field_names.append(f) 681 697 else: 682 # Default to all fields.683 field_names = [f.attname for f in self.model._meta. fields]698 # Default to all non-lazy fields. 699 field_names = [f.attname for f in self.model._meta.default_select_fields] 684 700 685 701 self.query.add_fields(field_names, False) 686 self.query.default_cols = False687 702 self.field_names = field_names 688 703 689 704 def _clone(self, klass=None, setup=False, **kwargs): … … 788 803 789 804 790 805 def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0, 791 requested=None ):806 requested=None, fields=None): 792 807 """ 793 808 Helper function that recursively returns an object with the specified 794 809 related attributes already populated. … … 797 812 # We've recursed deeply enough; stop now. 798 813 return None 799 814 815 if fields is None: 816 fields = klass._meta.default_select_fields 817 800 818 restricted = requested is not None 801 index_end = index_start + len( klass._meta.fields)802 fields = row[index_start:index_end]819 index_end = index_start + len(fields) 820 values = row[index_start:index_end] 803 821 if not [x for x in fields if x is not None]: 804 822 # If we only have a list of Nones, there was not related object. 805 823 obj = None 806 824 else: 807 obj = klass (*fields)825 obj = klass._new_from_select_values(values, fields) 808 826 for f in klass._meta.fields: 809 827 if not select_related_descend(f, restricted, requested): 810 828 continue -
django/contrib/gis/db/models/sql/query.py
40 40 obj.extra_select_fields = self.extra_select_fields.copy() 41 41 return obj 42 42 43 def get_columns(self, with_aliases=False):43 def _get_field_select(self, table_alias, field): 44 44 """ 45 Return the list of columns to use in the select statement. If no 46 columns have been specified, returns all columns relating to fields in 47 the model. 48 49 If 'with_aliases' is true, any column names that are duplicated 50 (without the table names) are given unique aliases. This is needed in 51 some cases to avoid ambiguitity with nested queries. 52 53 This routine is overridden from Query to handle customized selection of 54 geometry columns. 55 """ 56 qn = self.quote_name_unless_alias 57 qn2 = self.connection.ops.quote_name 58 result = ['(%s) AS %s' % (self.get_extra_select_format(alias) % col[0], qn2(alias)) 59 for alias, col in self.extra_select.iteritems()] 60 aliases = set(self.extra_select.keys()) 61 if with_aliases: 62 col_aliases = aliases.copy() 45 Returns the SELECT SQL string for the given field. Figures out 46 if any custom selection SQL is needed for the column The `alias` 47 keyword may be used to manually specify the database table where 48 the column exists, if not in the model associated with this 49 `GeoQuery`. 50 """ 51 if field in self.custom_select: 52 return self.get_select_format(field) % self.custom_select[field] 63 53 else: 64 col_aliases = set() 65 if self.select: 66 # This loop customized for GeoQuery. 67 for col, field in izip(self.select, self.select_fields): 68 if isinstance(col, (list, tuple)): 69 r = self.get_field_select(field, col[0]) 70 if with_aliases and col[1] in col_aliases: 71 c_alias = 'Col%d' % len(col_aliases) 72 result.append('%s AS %s' % (r, c_alias)) 73 aliases.add(c_alias) 74 col_aliases.add(c_alias) 75 else: 76 result.append(r) 77 aliases.add(r) 78 col_aliases.add(col[1]) 79 else: 80 result.append(col.as_sql(quote_func=qn)) 81 if hasattr(col, 'alias'): 82 aliases.add(col.alias) 83 col_aliases.add(col.alias) 84 elif self.default_cols: 85 cols, new_aliases = self.get_default_columns(with_aliases, 86 col_aliases) 87 result.extend(cols) 88 aliases.update(new_aliases) 89 # This loop customized for GeoQuery. 90 if not self.aggregate: 91 for (table, col), field in izip(self.related_select_cols, self.related_select_fields): 92 r = self.get_field_select(field, table) 93 if with_aliases and col in col_aliases: 94 c_alias = 'Col%d' % len(col_aliases) 95 result.append('%s AS %s' % (r, c_alias)) 96 aliases.add(c_alias) 97 col_aliases.add(c_alias) 98 else: 99 result.append(r) 100 aliases.add(r) 101 col_aliases.add(col) 54 return super(GeoQuery, self)._get_field_select(table_alias, field) 102 55 103 self._select_aliases = aliases104 return result105 106 def get_default_columns(self, with_aliases=False, col_aliases=None,107 start_alias=None, opts=None, as_pairs=False):108 """109 Computes the default columns for selecting every field in the base110 model.111 112 Returns a list of strings, quoted appropriately for use in SQL113 directly, as well as a set of aliases used in the select statement.114 115 This routine is overridden from Query to handle customized selection of116 geometry columns.117 """118 result = []119 if opts is None:120 opts = self.model._meta121 if start_alias:122 table_alias = start_alias123 else:124 table_alias = self.tables[0]125 root_pk = self.model._meta.pk.column126 seen = {None: table_alias}127 aliases = set()128 for field, model in opts.get_fields_with_model():129 try:130 alias = seen[model]131 except KeyError:132 alias = self.join((table_alias, model._meta.db_table,133 root_pk, model._meta.pk.column))134 seen[model] = alias135 if as_pairs:136 result.append((alias, field.column))137 continue138 # This part of the function is customized for GeoQuery. We139 # see if there was any custom selection specified in the140 # dictionary, and set up the selection format appropriately.141 field_sel = self.get_field_select(field, alias)142 if with_aliases and field.column in col_aliases:143 c_alias = 'Col%d' % len(col_aliases)144 result.append('%s AS %s' % (field_sel, c_alias))145 col_aliases.add(c_alias)146 aliases.add(c_alias)147 else:148 r = field_sel149 result.append(r)150 aliases.add(r)151 if with_aliases:152 col_aliases.add(field.column)153 if as_pairs:154 return result, None155 return result, aliases156 157 56 def get_ordering(self): 158 57 """ 159 58 This routine is overridden to disable ordering for aggregate … … 209 108 sel_fmt = sel_fmt % self.custom_select[alias] 210 109 return sel_fmt 211 110 212 def get_field_select(self, fld, alias=None):213 """214 Returns the SELECT SQL string for the given field. Figures out215 if any custom selection SQL is needed for the column The `alias`216 keyword may be used to manually specify the database table where217 the column exists, if not in the model associated with this218 `GeoQuery`.219 """220 sel_fmt = self.get_select_format(fld)221 if fld in self.custom_select:222 field_sel = sel_fmt % self.custom_select[fld]223 else:224 field_sel = sel_fmt % self._field_column(fld, alias)225 return field_sel226 227 111 def get_select_format(self, fld): 228 112 """ 229 113 Returns the selection format string, depending on the requirements -
tests/regressiontests/lazy_fields/models.py
1 from django.db import models 2 3 class LazyObject3(models.Model): 4 lazy = models.IntegerField(lazy=True) 5 normal = models.IntegerField() 6 7 class LazyObject2(models.Model): 8 normal = models.IntegerField() 9 fk3 = models.ForeignKey(LazyObject3) 10 11 class LazyObject(models.Model): 12 normal = models.IntegerField() 13 lazy = models.IntegerField(lazy=True) 14 fk3 = models.ForeignKey(LazyObject3) 15 fk2 = models.ForeignKey(LazyObject2) 16 lazy2 = models.IntegerField(lazy=True, default=42) 17 18 def checkUnchanged(obj, fieldName): 19 field = obj._meta.get_field(fieldName) 20 assert not field.write_on_save(obj) 21 22 def checkChanged(obj, fieldName): 23 field = obj._meta.get_field(fieldName) 24 assert field.write_on_save(obj) 25 26 def checkLazyField(obj, fieldName, value, cached=False): 27 if not cached: 28 assert not hasattr(obj,'_%s_lcache' % fieldName) 29 else: 30 assert getattr(obj, '_%s_lcache' % fieldName) == value 31 32 assert getattr(obj, fieldName) == value 33 assert getattr(obj, '_%s_lcache' % fieldName) == value 34 35 def check3(_o3, correct3, eager=[]): 36 checkLazyField(_o3, 'lazy', correct3.lazy, cached=('lazy' in eager)) 37 38 assert _o3.normal == correct3.normal 39 40 checkUnchanged(_o3, 'lazy') 41 _o3.lazy = 11 42 checkChanged(_o3, 'lazy') 43 checkLazyField(_o3, 'lazy', 11, cached=True) 44 45 def check2(_o2, correct2, relatedDepth=0): 46 47 if relatedDepth > 0: 48 assert hasattr(_o2, '_fk3_cache') 49 else: 50 assert not hasattr(_o2, '_fk3_cache') 51 52 check3(_o2.fk3, correct2.fk3) 53 assert hasattr(_o2, '_fk3_cache') 54 55 assert _o2.normal == correct2.normal 56 57 def check1(_o1, correct1, eager=[], relatedDepth=0): 58 59 if relatedDepth > 0: 60 assert hasattr(_o1, '_fk2_cache') 61 assert hasattr(_o1, '_fk3_cache') 62 if relatedDepth > 1: 63 assert hasattr(_o1._fk2_cache,'_fk3_cache') 64 else: 65 assert not hasattr(_o1._fk2_cache,'_fk3_cache') 66 else: 67 assert not hasattr(_o1, '_fk2_cache') 68 assert not hasattr(_o1, '_fk3_cache') 69 70 check2(_o1.fk2, correct1.fk2, relatedDepth=relatedDepth - 1) 71 assert hasattr(_o1, '_fk2_cache') 72 73 check3(_o1.fk3, correct1.fk3) 74 assert hasattr(_o1, '_fk3_cache') 75 76 assert _o1.normal == correct1.normal 77 78 checkLazyField(_o1, 'lazy', correct1.lazy, cached=('lazy' in eager)) 79 checkUnchanged(_o1, 'lazy') 80 checkUnchanged(_o1, 'lazy2') 81 checkLazyField(_o1, 'lazy2', correct1.lazy2, cached=('lazy2' in eager)) 82 83 def checkAll(correct1, correct2, correct3): 84 "Test operation of lazy fields using toggle_fields and/or select_related" 85 86 check3(LazyObject3.objects.get(lazy=correct3.lazy), correct3) 87 88 check3(LazyObject3.objects.toggle_fields(fetch=['lazy']).get(lazy=correct3.lazy), correct3, eager=['lazy']) 89 90 check3(LazyObject3.objects.get(normal=correct3.normal), correct3) 91 92 check2(LazyObject2.objects.get(normal=correct2.normal), correct2) 93 94 check2(LazyObject2.objects.select_related().get(normal=correct2.normal), correct2, relatedDepth=1) 95 96 check1(LazyObject.objects.get(normal=correct1.normal), correct1) 97 98 check1(LazyObject.objects.toggle_fields(fetch=['lazy']).get(normal=correct1.normal), correct1, eager=['lazy']) 99 100 check1(LazyObject.objects.toggle_fields(fetch=['lazy','lazy2']).get(normal=correct1.normal), correct1, eager=['lazy','lazy2']) 101 102 check1(LazyObject.objects.select_related(depth=1).get(normal=correct1.normal), correct1, relatedDepth=1) 103 104 check1(LazyObject.objects.select_related(depth=2).get(normal=correct1.normal), correct1, relatedDepth=2) 105 106 check1(LazyObject.objects.toggle_fields(fetch=['lazy']).select_related(depth=1).get(normal=correct1.normal), correct1, eager=['lazy'], relatedDepth=1) 107 108 def test(): 109 110 LazyObject.objects.all().delete() 111 LazyObject2.objects.all().delete() 112 LazyObject3.objects.all().delete() 113 114 # desynchronize auto-increment ids of the 3 object types 115 LazyObject3.objects.create(lazy=999,normal=999) 116 o3_x = LazyObject3.objects.create(lazy=999,normal=999) 117 LazyObject2.objects.create(normal=999,fk3=o3_x) 118 119 class orig3: 120 lazy=1 121 normal=2 122 123 class orig2: 124 normal=3 125 fk3=orig3 126 127 class orig1: 128 normal=4 129 lazy=5 130 lazy2=6 131 fk2=orig2 132 fk3=orig3 133 134 # create the test objects 135 o3 = LazyObject3.objects.create(lazy=orig3.lazy,normal=orig3.normal) 136 o2 = LazyObject2.objects.create(normal=orig2.normal,fk3 =o3) 137 o1 = LazyObject.objects.create(normal=orig1.normal,lazy=orig1.lazy,fk3=o3,fk2=o2,lazy2=orig1.lazy2) 138 139 # test default values of lazy fields 140 empty_o1 = LazyObject() 141 142 checkLazyField(empty_o1, 'lazy', None, cached=False) 143 checkLazyField(empty_o1, 'lazy2', 42, cached=False) 144 145 # test toggle_fields on an object with existing default-lazy fields 146 lazy_o1 = LazyObject.objects.toggle_fields(lazy=['normal'], fetch=['lazy2']).get(lazy=orig1.lazy) 147 148 checkLazyField(lazy_o1, 'normal', orig1.normal, cached=False) 149 checkLazyField(lazy_o1, 'lazy', orig1.lazy, cached=False) 150 checkLazyField(lazy_o1, 'lazy2', orig1.lazy2, cached=True) 151 152 # test toggle_fields on an object that doesn't have any default-lazy fields 153 lazy_o2 = LazyObject2.objects.toggle_fields(lazy=['normal']).get(fk3__lazy=orig2.fk3.lazy) 154 checkLazyField(lazy_o2, 'normal', orig2.normal, cached=False) 155 156 # test toggle_fields with fetch_only, and lazying a foreign key 157 lazy_o1 = LazyObject.objects.toggle_fields(fetch_only=['lazy','fk3']).get(lazy=orig1.lazy) 158 checkLazyField(lazy_o1, 'normal', orig1.normal, cached=False) 159 checkLazyField(lazy_o1, 'lazy', orig1.lazy, cached=True) 160 checkLazyField(lazy_o1, 'lazy2', orig1.lazy2, cached=False) 161 162 checkLazyField(lazy_o1, 'fk2_id', o2.id, cached=False) 163 164 assert lazy_o1.fk2.normal == orig2.normal 165 assert lazy_o1.fk2_id == o2.id # make sure lazy fk2_id and fk2 cache don't overwrite each other. 166 167 assert lazy_o1.fk3_id == o3.id 168 169 # test saving lazy fields 170 171 checkAll(orig1, orig2, orig3) 172 173 orig3_a = orig3() 174 orig3_a.normal = 9 175 o3.normal = 9 176 o3.save() 177 178 orig2_a = orig2() 179 orig2_a.normal = 8 180 orig2_a.fk3 = orig3_a 181 o2.normal = 8 182 o2.save() 183 184 orig1_a = orig1() 185 orig1_a.lazy = 7 186 orig1_a.fk3 = orig3_a 187 orig1_a.fk2 = orig2_a 188 o1.lazy = 7 189 o1.save() 190 191 checkAll(orig1_a, orig2_a, orig3_a) 192 193 orig1_b = orig1_a 194 orig1_b.lazy = 10 195 o1.lazy = 10 196 orig1_b.lazy2 = 11 197 o1.lazy2 = 11 198 o1.save() 199 200 checkAll(orig1_b, orig2_a, orig3_a) 201 return "OK" 202 203 __test__ = {'API_TESTS':""" 204 >>> test() 205 "OK" 206 """}