django-ormcache 1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_ormcache-1.4.dist-info/METADATA +69 -0
- django_ormcache-1.4.dist-info/RECORD +12 -0
- django_ormcache-1.4.dist-info/WHEEL +5 -0
- django_ormcache-1.4.dist-info/licenses/LICENSE +20 -0
- django_ormcache-1.4.dist-info/top_level.txt +1 -0
- ormcache/__init__.py +0 -0
- ormcache/decorators.py +57 -0
- ormcache/managers.py +59 -0
- ormcache/models.py +0 -0
- ormcache/queryset.py +149 -0
- ormcache/signals.py +6 -0
- ormcache/utils.py +54 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-ormcache
|
|
3
|
+
Version: 1.4
|
|
4
|
+
Summary: ORM cache for Django
|
|
5
|
+
Home-page: https://github.com/educreations/django-ormcache
|
|
6
|
+
Author: Corey Farwell
|
|
7
|
+
Author-email: coreyf@rwell.org
|
|
8
|
+
Maintainer: Educreations Engineering
|
|
9
|
+
Maintainer-email: engineering@educreations.com
|
|
10
|
+
License: MIT
|
|
11
|
+
Project-URL: Homepage, https://github.com/educreations/django-ormcache
|
|
12
|
+
Project-URL: Issues, https://github.com/educreations/django-ormcache/issues
|
|
13
|
+
Project-URL: Changelog, https://github.com/educreations/django-ormcache/blob/master/CHANGES.md
|
|
14
|
+
Keywords: cache django
|
|
15
|
+
Classifier: Framework :: Django
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Topic :: Software Development
|
|
21
|
+
Requires-Python: >=3.8, <3.13
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: Django<6.0,>=4.2
|
|
25
|
+
Requires-Dist: six
|
|
26
|
+
Provides-Extra: test
|
|
27
|
+
Requires-Dist: tox; extra == "test"
|
|
28
|
+
Requires-Dist: pytest; extra == "test"
|
|
29
|
+
Requires-Dist: pytest-django; extra == "test"
|
|
30
|
+
Requires-Dist: flake8; extra == "test"
|
|
31
|
+
Dynamic: author
|
|
32
|
+
Dynamic: author-email
|
|
33
|
+
Dynamic: classifier
|
|
34
|
+
Dynamic: description
|
|
35
|
+
Dynamic: description-content-type
|
|
36
|
+
Dynamic: home-page
|
|
37
|
+
Dynamic: keywords
|
|
38
|
+
Dynamic: license
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
Dynamic: maintainer
|
|
41
|
+
Dynamic: maintainer-email
|
|
42
|
+
Dynamic: project-url
|
|
43
|
+
Dynamic: provides-extra
|
|
44
|
+
Dynamic: requires-dist
|
|
45
|
+
Dynamic: requires-python
|
|
46
|
+
Dynamic: summary
|
|
47
|
+
|
|
48
|
+
# Django ORMCache
|
|
49
|
+
|
|
50
|
+
A cache manager mixin that provides some caching of objects for the ORM.
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Installation / Setup / Usage
|
|
54
|
+
|
|
55
|
+
TODO
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# Testing
|
|
59
|
+
|
|
60
|
+
Run the tests with:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
pip install -e .[tests]
|
|
64
|
+
tox
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
# License
|
|
68
|
+
|
|
69
|
+
Copyright © 2024, Educreations, Inc under the [MIT](LICENSE).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
django_ormcache-1.4.dist-info/licenses/LICENSE,sha256=RVUF4JTQmXwGoB2iQ_ywDydZlJ0ILqbUgdN7DxvayiA,1085
|
|
2
|
+
ormcache/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
ormcache/decorators.py,sha256=2ZMqQTIJlK0uLCf4Egk5rZhp4WrBzeGyaIScWpT3yp4,1822
|
|
4
|
+
ormcache/managers.py,sha256=s1r9FxqN6pyxxM22aUqdI9IK22a37tTPbtB-gLBlNrQ,2034
|
|
5
|
+
ormcache/models.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
ormcache/queryset.py,sha256=uTFu2IUk16_R9hIZrXjuZQAEllrK-xOr1oVtkOh7cI4,5438
|
|
7
|
+
ormcache/signals.py,sha256=4v7Awa3QIH9pGd-VbEk9jcjQvwBABq-29fmn9p6z1lg,147
|
|
8
|
+
ormcache/utils.py,sha256=RSI___JrEEDEzwb-MjhqtDojYvsoX0qD-6gAObNbkBc,1792
|
|
9
|
+
django_ormcache-1.4.dist-info/METADATA,sha256=cvBPGc2_neM4K27oiCMM6dJAxYbZJqM7_nswsSiyn4M,1763
|
|
10
|
+
django_ormcache-1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
11
|
+
django_ormcache-1.4.dist-info/top_level.txt,sha256=aOHDwDLnkCYseV7Vm27Flc3fKlms_YQQnElZh0fSgR8,9
|
|
12
|
+
django_ormcache-1.4.dist-info/RECORD,,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017 Educreations, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ormcache
|
ormcache/__init__.py
ADDED
|
File without changes
|
ormcache/decorators.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from hashlib import sha1
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from django.core.cache import cache
|
|
5
|
+
from django.utils.decorators import wraps
|
|
6
|
+
|
|
7
|
+
log = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def cache_result(ttl=60 * 5, key=None):
|
|
11
|
+
"""
|
|
12
|
+
Cache the result of a function call using Django's caching mechanism.
|
|
13
|
+
|
|
14
|
+
If the decorated function returns None, cache_result raises a
|
|
15
|
+
RuntimeException (as the cache returns None to indicate a miss).
|
|
16
|
+
|
|
17
|
+
Note: If a key is not passed, the decorator will hash the function
|
|
18
|
+
signature. The ordering of function parameters is important. e.g.:
|
|
19
|
+
* myFunction(x=1, y=2)
|
|
20
|
+
* myFunction(y=2, x=1)
|
|
21
|
+
* myFunction(1, 2)
|
|
22
|
+
will each be cached separately.
|
|
23
|
+
|
|
24
|
+
Note: Does not automatically handle invalidation
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
--------------------------------------------------------------------------
|
|
28
|
+
@cache_result
|
|
29
|
+
def my_expensive_method(param1, param2, param3):
|
|
30
|
+
....
|
|
31
|
+
return expensive_result
|
|
32
|
+
--------------------------------------------------------------------------
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def decorator(func):
|
|
36
|
+
@wraps(func)
|
|
37
|
+
def inner(*args, **kwargs):
|
|
38
|
+
key_ = key
|
|
39
|
+
if key_ is None:
|
|
40
|
+
func_signature = (
|
|
41
|
+
str(func.__module__) + str(func.__name__) + str(args) + str(kwargs)
|
|
42
|
+
).encode("utf8")
|
|
43
|
+
key_ = sha1(func_signature).hexdigest()
|
|
44
|
+
result = cache.get(key_)
|
|
45
|
+
if result is None:
|
|
46
|
+
result = func(*args, **kwargs)
|
|
47
|
+
if result is None:
|
|
48
|
+
log.error(
|
|
49
|
+
"Function decorated by @cache_result returned " "None",
|
|
50
|
+
extra={"function": func.__name__},
|
|
51
|
+
)
|
|
52
|
+
cache.set(key_, result, ttl)
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
return inner
|
|
56
|
+
|
|
57
|
+
return decorator
|
ormcache/managers.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from django.db.models.signals import class_prepared, post_delete, post_save
|
|
2
|
+
from django.utils.functional import cached_property
|
|
3
|
+
|
|
4
|
+
from ormcache.queryset import CachedQuerySet
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CachedManagerMixin(object):
|
|
8
|
+
@cached_property
|
|
9
|
+
def __cache_enabled(self):
|
|
10
|
+
return getattr(self.model, "cache_enabled", False)
|
|
11
|
+
|
|
12
|
+
def __require_cache(func):
|
|
13
|
+
def wrapper(self, *args, **kwargs):
|
|
14
|
+
if not self.__cache_enabled:
|
|
15
|
+
error = "Caching is not enabled on {}".format(str(type(self)))
|
|
16
|
+
raise RuntimeError(error)
|
|
17
|
+
return func(self, *args, **kwargs)
|
|
18
|
+
|
|
19
|
+
return wrapper
|
|
20
|
+
|
|
21
|
+
@__require_cache
|
|
22
|
+
def from_ids(self, ids, lookup="pk__in", **kwargs):
|
|
23
|
+
queryset = self.get_queryset()
|
|
24
|
+
return queryset.from_ids(ids, lookup=lookup, **kwargs)
|
|
25
|
+
|
|
26
|
+
@__require_cache
|
|
27
|
+
def invalidate(self, *args, **kwargs):
|
|
28
|
+
return self.get_queryset().invalidate(*args, **kwargs)
|
|
29
|
+
|
|
30
|
+
@__require_cache
|
|
31
|
+
def cache_key(self, *args, **kwargs):
|
|
32
|
+
return self.get_queryset().cache_key(*args, **kwargs)
|
|
33
|
+
|
|
34
|
+
# Django overrides
|
|
35
|
+
|
|
36
|
+
def contribute_to_class(self, model, name):
|
|
37
|
+
"""Override Django builtin"""
|
|
38
|
+
super(CachedManagerMixin, self).contribute_to_class(model, name)
|
|
39
|
+
class_prepared.connect(self.__class_prepared_cache, sender=model)
|
|
40
|
+
|
|
41
|
+
def get_queryset(self):
|
|
42
|
+
"""Override Django builtin"""
|
|
43
|
+
if self.__cache_enabled:
|
|
44
|
+
return CachedQuerySet(self.model)
|
|
45
|
+
else:
|
|
46
|
+
return super(CachedManagerMixin, self).get_queryset()
|
|
47
|
+
|
|
48
|
+
# Signals
|
|
49
|
+
|
|
50
|
+
def __class_prepared_cache(self, sender, **kwargs):
|
|
51
|
+
if self.__cache_enabled:
|
|
52
|
+
post_save.connect(self.__post_save_cache, sender=self.model, weak=False)
|
|
53
|
+
post_delete.connect(self.__post_delete_cache, sender=self.model, weak=False)
|
|
54
|
+
|
|
55
|
+
def __post_save_cache(self, instance, created, **kwargs):
|
|
56
|
+
self.invalidate(instance.pk, recache=True)
|
|
57
|
+
|
|
58
|
+
def __post_delete_cache(self, instance, **kwargs):
|
|
59
|
+
self.invalidate(instance.pk)
|
ormcache/models.py
ADDED
|
File without changes
|
ormcache/queryset.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from __future__ import unicode_literals
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from collections.abc import Iterable
|
|
5
|
+
except ImportError:
|
|
6
|
+
from collections import Iterable
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
import django
|
|
11
|
+
from django.core.cache import cache
|
|
12
|
+
from django.db.models.query import QuerySet
|
|
13
|
+
|
|
14
|
+
if django.VERSION >= (1, 8):
|
|
15
|
+
from django.db.models.expressions import Col
|
|
16
|
+
from django.db.models.lookups import Exact
|
|
17
|
+
elif django.VERSION >= (1, 7):
|
|
18
|
+
from django.db.models.lookups import Exact
|
|
19
|
+
from django.db.models.sql.datastructures import Col
|
|
20
|
+
|
|
21
|
+
from ormcache.signals import cache_hit, cache_missed, cache_invalidated # noqa
|
|
22
|
+
|
|
23
|
+
log = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CachedQuerySet(QuerySet):
|
|
27
|
+
__MAXIMUM_CACHE_KEY_LENGTH = 250
|
|
28
|
+
__CACHE_FOREVER = 2592000 # http://ur1.ca/egyvu
|
|
29
|
+
|
|
30
|
+
def get(self, *args, **kwargs):
|
|
31
|
+
"""
|
|
32
|
+
Adds a layer of caching around the Manager's built in 'get()' method.
|
|
33
|
+
Will only cache if 'pk' or 'id' is used in kwargs. Results from the
|
|
34
|
+
'get()' method will be cached indefinitely (30 days) until invalidated.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
orig_kwargs = kwargs.copy()
|
|
38
|
+
|
|
39
|
+
# If this queryset is filtered by a single column and no arguments
|
|
40
|
+
# were passed to get(), treat the queryset filter as an argument to
|
|
41
|
+
# get(). This lets Model.objects.filter(pk=42).get() work like
|
|
42
|
+
# Model.objects.get(pk=42).
|
|
43
|
+
can_ignore_filter = False
|
|
44
|
+
if django.VERSION >= (1, 7) and not kwargs:
|
|
45
|
+
if len(self.query.where) == 1 and not self.query.where.negated:
|
|
46
|
+
(child,) = self.query.where.children
|
|
47
|
+
if isinstance(child, Exact) and isinstance(child.lhs, Col):
|
|
48
|
+
can_ignore_filter = True
|
|
49
|
+
kwargs[child.lhs.target.attname] = child.rhs
|
|
50
|
+
elif (
|
|
51
|
+
len(args) == 1
|
|
52
|
+
and len(args[0].children) == 1
|
|
53
|
+
and len(args[0].children[0]) == 2
|
|
54
|
+
and args[0].children[0][0] == "id"
|
|
55
|
+
):
|
|
56
|
+
can_ignore_filter = True
|
|
57
|
+
kwargs[args[0].children[0][0]] = args[0].children[0][1]
|
|
58
|
+
|
|
59
|
+
# Don't access cache if using a filtered queryset
|
|
60
|
+
if self.query.where and not can_ignore_filter:
|
|
61
|
+
return super(CachedQuerySet, self).get(*args, **orig_kwargs)
|
|
62
|
+
|
|
63
|
+
# Don't access cache if using a deferred queryset
|
|
64
|
+
if (
|
|
65
|
+
len(self.query.deferred_loading[0]) > 0
|
|
66
|
+
or not self.query.deferred_loading[1]
|
|
67
|
+
):
|
|
68
|
+
return super(CachedQuerySet, self).get(*args, **orig_kwargs)
|
|
69
|
+
|
|
70
|
+
# Get the cache key from the model name and pk
|
|
71
|
+
if "pk" in kwargs:
|
|
72
|
+
pk = kwargs["pk"]
|
|
73
|
+
elif "pk__exact" in kwargs:
|
|
74
|
+
pk = kwargs["pk__exact"]
|
|
75
|
+
elif "id" in kwargs:
|
|
76
|
+
pk = kwargs["id"]
|
|
77
|
+
elif "id__exact" in kwargs:
|
|
78
|
+
pk = kwargs["id__exact"]
|
|
79
|
+
else:
|
|
80
|
+
return super(CachedQuerySet, self).get(*args, **orig_kwargs)
|
|
81
|
+
|
|
82
|
+
key = self.cache_key(pk)
|
|
83
|
+
|
|
84
|
+
# Retrieve (or set) the item in the cache
|
|
85
|
+
item = cache.get(key)
|
|
86
|
+
if item is None:
|
|
87
|
+
cache_missed.send(sender=self.model)
|
|
88
|
+
item = super(CachedQuerySet, self).get(*args, **orig_kwargs)
|
|
89
|
+
cache.set(key, item, self.__CACHE_FOREVER)
|
|
90
|
+
else:
|
|
91
|
+
cache_hit.send(sender=self.model)
|
|
92
|
+
|
|
93
|
+
return item
|
|
94
|
+
|
|
95
|
+
def from_ids(self, ids, lookup="pk__in"):
|
|
96
|
+
assert isinstance(ids, Iterable)
|
|
97
|
+
|
|
98
|
+
cache_keys = [self.cache_key(id_) for id_ in ids]
|
|
99
|
+
|
|
100
|
+
instances = cache.get_many(cache_keys).values()
|
|
101
|
+
cached_dict = {i.id: i for i in instances if i}
|
|
102
|
+
uncached_ids = set(ids) - set(cached_dict.keys())
|
|
103
|
+
|
|
104
|
+
# If there are uncached instances, retrieve and cache them
|
|
105
|
+
if len(uncached_ids) > 0:
|
|
106
|
+
uncached = self.filter(**{lookup: uncached_ids})
|
|
107
|
+
|
|
108
|
+
# Add the uncached instances to the existing instances
|
|
109
|
+
cached_dict.update((i.id, i) for i in uncached)
|
|
110
|
+
|
|
111
|
+
# Cache the uncached instances
|
|
112
|
+
to_cache = {self.cache_key(i.pk): i for i in uncached}
|
|
113
|
+
cache.set_many(to_cache, self.__CACHE_FOREVER)
|
|
114
|
+
|
|
115
|
+
return [cached_dict[id_] for id_ in ids if id_ in cached_dict]
|
|
116
|
+
|
|
117
|
+
def cache_key(self, pk):
|
|
118
|
+
"""Generate the cache key for an individual model"""
|
|
119
|
+
key = "{}-pk:{}".format(self.model.__name__, pk)
|
|
120
|
+
|
|
121
|
+
# We shouldn't have keys that are crazy long. This might mean
|
|
122
|
+
# we get duplicates though
|
|
123
|
+
key = key[: self.__MAXIMUM_CACHE_KEY_LENGTH]
|
|
124
|
+
|
|
125
|
+
return key
|
|
126
|
+
|
|
127
|
+
def invalidate(self, pk, recache=False):
|
|
128
|
+
"""Invalidate a single item in the cache"""
|
|
129
|
+
key = self.cache_key(pk)
|
|
130
|
+
cache_invalidated.send(sender=self.model)
|
|
131
|
+
if recache is True:
|
|
132
|
+
try:
|
|
133
|
+
entry = super(CachedQuerySet, self).get(pk=pk)
|
|
134
|
+
except self.model.DoesNotExist:
|
|
135
|
+
log.info(
|
|
136
|
+
"Unable to recache model during invalidate",
|
|
137
|
+
exc_info=True,
|
|
138
|
+
extra={"data": {"model": self.model, "pk": pk}},
|
|
139
|
+
)
|
|
140
|
+
except self.model.MultipleObjectsReturned:
|
|
141
|
+
log.error(
|
|
142
|
+
"Error retrieving single entry from database",
|
|
143
|
+
exc_info=True,
|
|
144
|
+
extra={"data": {"model": self.model, "pk": pk}},
|
|
145
|
+
)
|
|
146
|
+
else:
|
|
147
|
+
cache.set(key, entry, self.__CACHE_FOREVER)
|
|
148
|
+
else:
|
|
149
|
+
cache.delete(key)
|
ormcache/signals.py
ADDED
ormcache/utils.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from django.db.models.fields.related import SingleRelatedObjectDescriptor
|
|
3
|
+
except ImportError:
|
|
4
|
+
from django.db.models.fields.related_descriptors import (
|
|
5
|
+
ReverseOneToOneDescriptor as SingleRelatedObjectDescriptor,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _distinct(l):
|
|
10
|
+
"""Given an iterable will return a list of all distinct values."""
|
|
11
|
+
return list(set(l))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def attach_foreignkey(objects, field):
|
|
15
|
+
"""
|
|
16
|
+
Similar to the disqus's attach_foreignkey, but pulls items from the cache
|
|
17
|
+
if caching is enabled on the model
|
|
18
|
+
"""
|
|
19
|
+
is_foreignkey = isinstance(field, SingleRelatedObjectDescriptor)
|
|
20
|
+
|
|
21
|
+
if not is_foreignkey:
|
|
22
|
+
field = field.field
|
|
23
|
+
accessor = "_%s_cache" % field.name
|
|
24
|
+
model = field.remote_field.model
|
|
25
|
+
lookup = "%s__in" % "pk"
|
|
26
|
+
column = field.column
|
|
27
|
+
key = "pk"
|
|
28
|
+
else:
|
|
29
|
+
accessor = field.cache_name
|
|
30
|
+
field = field.related.field
|
|
31
|
+
model = field.model
|
|
32
|
+
lookup = "%s__in" % field.name
|
|
33
|
+
column = "pk"
|
|
34
|
+
key = field.column
|
|
35
|
+
|
|
36
|
+
# Ensure values are unique, do not contain already present values, and
|
|
37
|
+
# are not missing values specified in select_related
|
|
38
|
+
values = _distinct(
|
|
39
|
+
getattr(o, column) for o in objects if getattr(o, accessor, False) is False
|
|
40
|
+
)
|
|
41
|
+
if not values:
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
# Pull instances from cache if caching is enabled. This conditional logic
|
|
45
|
+
# can be removed when the UserManager inherits BaseManager
|
|
46
|
+
if getattr(model, "cache_enabled", False) is True:
|
|
47
|
+
instances = model.objects.from_ids(values, lookup=lookup)
|
|
48
|
+
else:
|
|
49
|
+
instances = model.objects.filter(**{lookup: values})
|
|
50
|
+
|
|
51
|
+
instances_dict = {getattr(i, key): i for i in instances}
|
|
52
|
+
|
|
53
|
+
for obj in objects:
|
|
54
|
+
setattr(obj, accessor, instances_dict.get(getattr(obj, column)))
|