Ticket #17332: 17332.diff

File 17332.diff, 14.4 KB (added by akaariai, 4 years ago)

POC patch

  • django/db/models/base.py

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    index ebd67be..116a086 100644
    a b import copy 
    22import sys
    33from functools import update_wrapper
    44from itertools import izip
     5import warnings
    56
    67import django.db.models.manager     # Imported to register signal handler.
    78from django.conf import settings
    class ModelState(object): 
    271272        # Necessary for correct validation of new instances of objects with explicit (non-auto) PKs.
    272273        # This impacts validation only; it has no effect on the actual save.
    273274        self.adding = True
     275        self.old_pk_values = {}
     276
     277    def update(self, obj, using, adding=False, pk_fields=None):
     278        if pk_fields is None:
     279            pk_fields = obj._meta.pk_fields
     280        self.db = using
     281        self.adding = adding
     282        for f in pk_fields:
     283            self.old_pk_values[f.attname] = f.to_immutable_value(obj)
     284
     285    def clear(self):
     286        self.db = None
     287        self.adding = True
     288        self.old_pk_values = {}
    274289
    275290class Model(object):
    276291    __metaclass__ = ModelBase
    class Model(object): 
    429444        return getattr(self, meta.pk.attname)
    430445
    431446    def _set_pk_val(self, value):
     447        if value is None:
     448            self._state.old_pk_values[self._meta.pk.attname] = None
    432449        return setattr(self, self._meta.pk.attname, value)
    433450
    434451    pk = property(_get_pk_val, _set_pk_val)
    class Model(object): 
    486503        if origin and not meta.auto_created:
    487504            signals.pre_save.send(sender=origin, instance=self, raw=raw, using=using)
    488505
     506        # Proxy-objects do not need saving. Skip to the parent objects.
     507        if meta.proxy:
     508            for parent, _ in meta.parents.items():
     509                self.save_base(cls=parent, origin=cls, using=using)
     510            return
    489511        # If we are in a raw save, save the object exactly as presented.
    490512        # That means that we don't try to be smart about saving attributes
    491513        # that might have come from the parent class - we just save the
    492514        # attributes we have been given to the class we have been given.
    493         # We also go through this process to defer the save of proxy objects
    494         # to their actual underlying model.
    495         if not raw or meta.proxy:
    496             if meta.proxy:
    497                 org = cls
    498             else:
    499                 org = None
     515        if not raw:
    500516            for parent, field in meta.parents.items():
    501517                # At this point, parent's primary key field may be unknown
    502518                # (for example, from administration form which doesn't fill
    class Model(object): 
    504520                if field and getattr(self, parent._meta.pk.attname) is None and getattr(self, field.attname) is not None:
    505521                    setattr(self, parent._meta.pk.attname, getattr(self, field.attname))
    506522
    507                 self.save_base(cls=parent, origin=org, using=using)
     523                self.save_base(cls=parent, origin=None, using=using)
    508524
    509525                if field:
    510526                    setattr(self, field.attname, self._get_pk_val(parent._meta))
    511             if meta.proxy:
    512                 return
    513 
    514         if not meta.proxy:
    515             non_pks = [f for f in meta.local_fields if not f.primary_key]
    516 
    517             # First, try an UPDATE. If that doesn't update anything, do an INSERT.
    518             pk_val = self._get_pk_val(meta)
    519             pk_set = pk_val is not None
    520             record_exists = True
    521             manager = cls._base_manager
    522             if pk_set:
    523                 # Determine whether a record with the primary key already exists.
    524                 if (force_update or (not force_insert and
    525                         manager.using(using).filter(pk=pk_val).exists())):
    526                     # It does already exist, so do an UPDATE.
    527                     if force_update or non_pks:
    528                         values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks]
    529                         if values:
    530                             rows = manager.using(using).filter(pk=pk_val)._update(values)
    531                             if force_update and not rows:
    532                                 raise DatabaseError("Forced update did not affect any rows.")
    533                 else:
    534                     record_exists = False
    535             if not pk_set or not record_exists:
    536                 if meta.order_with_respect_to:
    537                     # If this is a model with an order_with_respect_to
    538                     # autopopulate the _order field
    539                     field = meta.order_with_respect_to
    540                     order_value = manager.using(using).filter(**{field.name: getattr(self, field.attname)}).count()
    541                     self._order = order_value
    542 
    543                 fields = meta.local_fields
    544                 if not pk_set:
    545                     if force_update:
    546                         raise ValueError("Cannot force an update in save() with no primary key.")
    547                     fields = [f for f in fields if not isinstance(f, AutoField)]
    548527
     528        non_pks = [f for f in meta.local_fields if not f.primary_key]
     529        pk_val = self._get_pk_val(meta)
     530        pk_set = pk_val is not None
     531        record_exists = True
     532        manager = cls._base_manager
     533
     534        if pk_set and not self._state.adding:
     535            old_pk_val = self._state.old_pk_values[meta.pk.attname]
     536            if old_pk_val is not None and pk_val != old_pk_val:
     537                warnings.warn("Saving an object with changed primary key is deprecated. "
     538                              "You can use obj.pk = None; obj.pk = new_val to bypass this check.",
     539                              PendingDeprecationWarning)
     540
     541        # First, try an UPDATE. If that doesn't update anything, do an INSERT.
     542        if pk_set:
     543            # Determine whether a record with the primary key already exists.
     544            if (force_update or (not force_insert and
     545                    manager.using(using).filter(pk=pk_val).exists())):
     546                # It does already exist, so do an UPDATE.
     547                if force_update or non_pks:
     548                    values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks]
     549                    if values:
     550                        rows = manager.using(using).filter(pk=pk_val)._update(values)
     551                        if force_update and not rows:
     552                            raise DatabaseError("Forced update did not affect any rows.")
     553            else:
    549554                record_exists = False
    550 
    551                 update_pk = bool(meta.has_auto_field and not pk_set)
    552                 result = manager._insert([self], fields=fields, return_id=update_pk, using=using, raw=raw)
    553 
    554                 if update_pk:
    555                     setattr(self, meta.pk.attname, result)
     555        if not pk_set or not record_exists:
     556            if meta.order_with_respect_to:
     557                # If this is a model with an order_with_respect_to
     558                # autopopulate the _order field
     559                field = meta.order_with_respect_to
     560                order_value = manager.using(using).filter(**{field.name: getattr(self, field.attname)}).count()
     561                self._order = order_value
     562
     563            fields = meta.local_fields
     564            if not pk_set:
     565                if force_update:
     566                    raise ValueError("Cannot force an update in save() with no primary key.")
     567                fields = [f for f in fields if not isinstance(f, AutoField)]
     568
     569            record_exists = False
     570
     571            update_pk = bool(meta.has_auto_field and not pk_set)
     572            result = manager._insert([self], fields=fields, return_id=update_pk, using=using, raw=raw)
     573
     574            if update_pk:
     575                setattr(self, meta.pk.attname, result)
     576
     577        if origin:
     578            # We are at the topmost object - finalize the save
    556579            transaction.commit_unless_managed(using=using)
    557 
    558         # Store the database on which the object was saved
    559         self._state.db = using
    560         # Once saved, this is no longer a to-be-added instance.
    561         self._state.adding = False
    562 
    563         # Signal that the save is complete
    564         if origin and not meta.auto_created:
    565             signals.post_save.send(sender=origin, instance=self,
    566                 created=(not record_exists), raw=raw, using=using)
    567 
     580            self._state.update(self, using)
     581            if not meta.auto_created:
     582                # Signal that the save is complete
     583                signals.post_save.send(sender=origin, instance=self,
     584                    created=(not record_exists), raw=raw, using=using)
    568585
    569586    save_base.alters_data = True
    570587
    class Model(object): 
    575592        collector = Collector(using=using)
    576593        collector.collect([self])
    577594        collector.delete()
     595        self._state.clear()
    578596
    579597    delete.alters_data = True
    580598
  • django/db/models/fields/__init__.py

    diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
    index 04b13aa..1c6d796 100644
    a b class Field(object): 
    428428        """
    429429        return smart_unicode(self._get_val_from_obj(obj))
    430430
     431    def to_immutable_value(self, obj):
     432        """
     433        Returns a value which is guaranteed to be immutable. This is used to
     434        track changes in field values. The returned value should be usable in
     435        database lookups.
     436        """
     437        return getattr(obj, self.attname)
     438
    431439    def bind(self, fieldmapping, original, bound_field_class):
    432440        return bound_field_class(self, fieldmapping, original)
    433441
  • django/db/models/fields/files.py

    diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py
    index d4b1743..211d05a 100644
    a b class FileField(Field): 
    249249            file.save(file.name, file, save=False)
    250250        return file
    251251
     252    def to_immutable_value(self, obj):
     253        return getattr(obj, self.attname).name
     254
    252255    def contribute_to_class(self, cls, name):
    253256        super(FileField, self).contribute_to_class(cls, name)
    254257        setattr(cls, self.name, self.descriptor_class(self))
  • django/db/models/options.py

    diff --git a/django/db/models/options.py b/django/db/models/options.py
    index 0cd52a3..af33e62 100644
    a b class Options(object): 
    3535        self.db_tablespace = settings.DEFAULT_TABLESPACE
    3636        self.admin = None
    3737        self.meta = meta
     38        # The PK of the model's table
    3839        self.pk = None
    3940        self.has_auto_field, self.auto_field = False, None
    4041        self.abstract = False
    class Options(object): 
    105106            self.db_table = "%s_%s" % (self.app_label, self.module_name)
    106107            self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
    107108
     109
    108110    def _prepare(self, model):
    109111        if self.order_with_respect_to:
    110112            self.order_with_respect_to = self.get_field(self.order_with_respect_to)
    class Options(object): 
    164166            if hasattr(self, '_field_cache'):
    165167                del self._field_cache
    166168                del self._field_name_cache
     169                del self._pk_field_cache
    167170
    168171        if hasattr(self, '_name_map'):
    169172            del self._name_map
    class Options(object): 
    219222        return self._field_name_cache
    220223    fields = property(_fields)
    221224
     225    def _pk_fields(self):
     226        """
     227        All the PKs the model has, including parent tables.
     228        """
     229        try:
     230            self._field_name_cache
     231        except AttributeError:
     232            self._fill_fields_cache()
     233        return self._pk_field_cache
     234    pk_fields = property(_pk_fields)
     235
    222236    def get_fields_with_model(self):
    223237        """
    224238        Returns a sequence of (field, model) pairs for all fields. The "model"
    class Options(object): 
    242256        cache.extend([(f, None) for f in self.local_fields])
    243257        self._field_cache = tuple(cache)
    244258        self._field_name_cache = [x for x, _ in cache]
     259        self._pk_field_cache = tuple(x for x, _ in cache if x.primary_key)
    245260
    246261    def _many_to_many(self):
    247262        try:
  • django/db/models/query.py

    diff --git a/django/db/models/query.py b/django/db/models/query.py
    index 80cdefd..10a5527 100644
    a b class QuerySet(object): 
    278278                    init_list.append(field.attname)
    279279            model_cls = deferred_class_factory(self.model, skip)
    280280
    281         # Cache db and model outside the loop
     281        # Cache some values outside the loop
    282282        db = self.db
    283283        model = self.model
    284284        compiler = self.query.get_compiler(using=db)
     285        pk_fields = model._meta.pk_fields
    285286        if fill_cache:
    286287            klass_info = get_klass_info(model, max_depth=max_depth,
    287288                                        requested=requested, only_load=only_load)
    class QuerySet(object): 
    297298                    # Omit aggregates in object creation.
    298299                    obj = model(*row[index_start:aggregate_start])
    299300
    300                 # Store the source database of the object
    301                 obj._state.db = db
    302                 # This object came from the database; it's not being added.
    303                 obj._state.adding = False
     301                obj._state.update(obj, db, pk_fields=pk_fields)
    304302
    305303            if extra_select:
    306304                for i, k in enumerate(extra_select):
    def get_klass_info(klass, max_depth=0, cur_depth=0, requested=None, 
    13161314    return klass, field_names, field_count, related_fields, reverse_related_fields
    13171315
    13181316
    1319 def get_cached_row(row, index_start, using,  klass_info, offset=0):
     1317def get_cached_row(row, index_start, using, klass_info, offset=0):
    13201318    """
    13211319    Helper function that recursively returns an object with the specified
    13221320    related attributes already populated.
    def get_cached_row(row, index_start, using, klass_info, offset=0): 
    13521350
    13531351    # If an object was retrieved, set the database state.
    13541352    if obj:
    1355         obj._state.db = using
    1356         obj._state.adding = False
     1353        obj._state.update(obj, using)
    13571354
    13581355    # Instantiate related fields
    13591356    index_end = index_start + field_count + offset
    class RawQuerySet(object): 
    14861483                for column, pos in annotation_fields:
    14871484                    setattr(instance, column, values[pos])
    14881485
    1489             instance._state.db = db
    1490             instance._state.adding = False
    1491 
     1486            instance._state.update(instance, db)
    14921487            yield instance
    14931488
    14941489    def __repr__(self):
Back to Top