Ticket #17: 17_no_tests.diff

File 17_no_tests.diff, 10.8 KB (added by PhiR, 7 years ago)

cleanup and comments. Not working but comments welcome

  • django/db/models/base.py

     
    1515from django.utils.encoding import smart_str, force_unicode, smart_unicode
    1616from django.conf import settings
    1717from itertools import izip
     18from weakref import WeakValueDictionary
    1819import types
    1920import sys
    2021import os
     
    7778        # registered version.
    7879        return get_model(new_class._meta.app_label, name, False)
    7980
     81    def __call__(cls, *args, **kwargs):
     82        """
     83        this method will either create an instance (by calling the default implementation)
     84        or try to retrieve one from the class-wide cache by infering the pk value from
     85        args and kwargs. If instance caching is enabled for this class, the cache is
     86        populated whenever possible (ie when it is possible to infer the pk value).
     87        """
     88        def new_instance():
     89            return super(ModelBase, cls).__call__(*args, **kwargs)
     90       
     91        # we always pop those settings from kwargs not to pollute the instance
     92        instance_caching_enabled = kwargs.pop('meta__instance_caching', False) or cls.meta__instance_caching
     93        # simplest case, just create a new instance every time
     94        if not instance_caching_enabled:
     95            return new_instance()
     96       
     97        instance_key = cls._get_cache_key(args, kwargs)
     98        # depending on the arguments, we might not be able to infer the PK, so in that case we create a new instance
     99        if instance_key is None:
     100            return new_instance()
     101
     102        cached_instance = cls.get_cached_instance(key)
     103        if cached_instance is None:
     104            cached_instance = new_instance()
     105            # FIXME test cached_instance._get_pk_val() ==  instance_key
     106            cls.cache_instance(cached_instance)
     107           
     108        return cached_instance
     109
    80110class Model(object):
    81111    __metaclass__ = ModelBase
    82112
     
    97127    def __ne__(self, other):
    98128        return not self.__eq__(other)
    99129
     130    def _get_cache_key(cls, args, kwargs):
     131        result = None
     132        pk = cls._meta.pk
     133        # get the index of the pk in the class fields. this should be calculated *once*, but isn't atm
     134        pk_position = cls._meta.fields.index(pk)
     135        # if it's in the args, we can get it easily by index
     136        if len(args) > pk_position:
     137            result = args[pk_position]
     138        # retrieve the pk value. Note that we use attname instead of name, to handle the case where the pk is a
     139        # a ForeignKey.
     140        elif pk.attname in kwargs:
     141            result = kwargs[pk.attname]
     142        # ok we couldn't find the value, but maybe it's a FK and we can find the corresponding object instead
     143        elif pk.name != pk.attname and pk.name in kwargs:
     144            result = kwargs[pk.name]
     145        # if the pk value happens to be a model (which can happen wich a FK), we'd rather use its own pk as the key
     146        if result is not None and isinstance(result, Model):
     147            result = result._get_pk_val()
     148        return result
     149    _get_cache_key = classmethod(_get_cache_key)
     150
     151    def get_cached_instance(cls, id):
     152        """
     153        Method to retrieve a cached instance by pk value. Returns None when not found (which will always be the case when caching is disabled for this class).
     154        """
     155        return cls.__instance_cache__.get(id)
     156    get_cached_instance = classmethod(get_cached_instance)
     157
     158    def cache_instance(cls, instance):
     159        """
     160        Method to store an instance in the cache. The instance will only be stored if 'instance.meta__instance_cache' is 'True', which means it is
     161        possible to override the class-wide settings in the instance.
     162        """
     163        if instance.meta__instance_cache and instance._get_pk_val() is not None:
     164            cls.__instance_cache__[instance._get_pk_val()] = instance
     165    cache_instance = classmethod(cache_instance)
     166
     167    def flush_cached_instance(cls, instance):
     168        """
     169        Method to flush an instance from the cache. The instance will always be flushed from the cache, since this is most likely called from delete().
     170        We do not test the pk value because delete() does it and it will fail silently anyway.
     171        """
     172        self.__instance_cache__.pop(_get_pk_val(), None)
     173
    100174    def __init__(self, *args, **kwargs):
    101175        dispatcher.send(signal=signals.pre_init, sender=self.__class__, args=args, kwargs=kwargs)
    102176
     
    197271        if hasattr(cls, 'get_absolute_url'):
    198272            cls.get_absolute_url = curry(get_absolute_url, opts, cls.get_absolute_url)
    199273
     274        cls.__instance_cache__ = WeakValueDictionary()
     275        # enable the cache according to user preferences (off by default)
     276        cls.meta__instance_caching = getattr(cls, 'meta__instance_caching', False)
     277
    200278        dispatcher.send(signal=signals.class_prepared, sender=cls)
    201279
    202280    _prepare = classmethod(_prepare)
     
    260338                setattr(self, self._meta.pk.attname, connection.ops.last_insert_id(cursor, self._meta.db_table, self._meta.pk.column))
    261339        transaction.commit_unless_managed()
    262340
     341        # if we're a new instance that hasn't been written in; save ourself.
     342        self.__class__.cache_instance(self)
     343
    263344        # Run any post-save hooks.
    264345        dispatcher.send(signal=signals.post_save, sender=self.__class__, instance=self)
    265346
     
    319400        seen_objs = SortedDict()
    320401        self._collect_sub_objects(seen_objs)
    321402
     403        # remove ourself from the cache
     404        self.__class__.flush_cached_instance(self)
    322405        # Actually delete the objects
    323406        delete_objects(seen_objs)
    324407
  • django/db/models/fields/related.py

     
    165165                if self.field.null:
    166166                    return None
    167167                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            # try to get a cached instance, and if that fails retrieve it from the db
     169            rel_obj = self.field.rel.to.get_cached_instance(val)
     170            if rel_obj is None:
     171                other_field = self.field.rel.get_related_field()
     172                if other_field.rel:
     173                    params = {'%s__pk' % self.field.rel.field_name: val}
     174                else:
     175                    params = {'%s__exact' % self.field.rel.field_name: val}
     176                rel_obj = self.field.rel.to._default_manager.get(**params)
    174177            setattr(instance, cache_name, rel_obj)
    175178            return rel_obj
    176179
  • django/db/models/query.py

     
    11341134            dispatcher.send(signal=signals.pre_delete, sender=cls, instance=instance)
    11351135
    11361136        pk_list = [pk for pk,instance in seen_objs[cls]]
     1137        # we wipe the cache now; it's *possible* some form of a __get__ lookup may reintroduce an item after
     1138        # the fact with the same pk (extremely unlikely)
     1139        for instance in seen_objs.values():
     1140            cls.flush_cached_instance(instance)
     1141
    11371142        for related in cls._meta.get_all_related_many_to_many_objects():
    11381143            if not isinstance(related.field, generic.GenericRelation):
    11391144                for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
     
    11671172    for cls in ordered_classes:
    11681173        seen_objs[cls].reverse()
    11691174        pk_list = [pk for pk,instance in seen_objs[cls]]
     1175        for instance in seen_objs.values():
     1176            cls.flush_cached_instance(instance)
    11701177        for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
    11711178            cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \
    11721179                (qn(cls._meta.db_table), qn(cls._meta.pk.column),
  • django/core/serializers/xml_serializer.py

     
    176176                else:
    177177                    value = field.to_python(getInnerText(field_node).strip())
    178178                data[field.name] = value
    179 
     179        data["meta__instance_cache"] = False
    180180        # Return a DeserializedObject so that the m2m data has a place to live.
    181181        return base.DeserializedObject(Model(**data), m2m_data)
    182182
     
    234234        else:
    235235           pass
    236236    return u"".join(inner_text)
    237 
  • django/core/serializers/python.py

     
    8888            # Handle all other fields
    8989            else:
    9090                data[field.name] = field.to_python(field_value)
    91 
     91        data["meta__instance_cache"] = False
    9292        yield base.DeserializedObject(Model(**data), m2m_data)
    9393
    9494def _get_model(model_identifier):
  • tests/modeltests/select_related/models.py

     
    1071071
    108108
    109109# 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)
    111111>>> db.reset_queries()
    112112>>> world = Species.objects.all()
    113113>>> [o.genus.family for o in world]
    114114[<Family: Drosophilidae>, <Family: Hominidae>, <Family: Fabaceae>, <Family: Amanitacae>]
    115115>>> len(db.connection.queries)
    116 9
     1165
    117117
    118118# With select_related():
    119119>>> db.reset_queries()
     
    129129>>> pea.genus.family.order.klass.phylum.kingdom.domain
    130130<Domain: Eukaryota>
    131131
    132 # Notice: one few query than above because of depth=1
     132# notice: instance caching saves the day; would be 7 without.
    133133>>> len(db.connection.queries)
    134 7
     1341
    135135
    136136>>> db.reset_queries()
    137137>>> pea = Species.objects.select_related(depth=5).get(name="sativum")
    138138>>> pea.genus.family.order.klass.phylum.kingdom.domain
    139139<Domain: Eukaryota>
    140140>>> len(db.connection.queries)
    141 3
     1411
    142142
    143143>>> db.reset_queries()
    144144>>> world = Species.objects.all().select_related(depth=2)
    145145>>> [o.genus.family.order for o in world]
    146146[<Order: Diptera>, <Order: Primates>, <Order: Fabales>, <Order: Agaricales>]
    147147>>> len(db.connection.queries)
    148 5
     1481
    149149
    150150# Reset DEBUG to where we found it.
    151151>>> settings.DEBUG = False
Back to Top