Skip to content

Commit a02d931

Browse files
jorgeecardonageorgedorn
authored andcommitted
Get multiple in one query (django-tastypie#1548)
* Use the method obj_get_list if implemented in get_multiple. * Move code outside the try/except block * Filter the queryset that return obj_get_list instead that uses the apply_filters mechanism, because it deletes the pk__in field * Add my name in AUTHORS * In get_multiple, return the objects in the same order as the pk are sent * Add test for get_multiple without a queryset * Remove whitespaces and blank lines
1 parent 9879a84 commit a02d931

File tree

3 files changed

+63
-11
lines changed

3 files changed

+63
-11
lines changed

AUTHORS

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Contributors:
3636
* Ed Summers (edsu) for a setup.py patch.
3737
* Sébastien Fievet (zyegfryed) for the initial OAuth implementation.
3838
* Jacob Kaplan-Moss (jacobkm) for the PATCH patch.
39-
* jorgeecardona for a patch in renaming the ``objects`` name of the response.
39+
* Jorge E. Cardona (jorgeecardona) for a patch in renaming the ``objects`` name of the response, and get_multiple in one query.
4040
* vbabiy for a patch on improved use of ``bundle.request`` & related resource validation.
4141
* philipn (Philip Neustrom) for GeoDjango integration.
4242
* dgerzo (Daniel Gerzo) for GeoDjango integration, work on PATCH and related fields, improving the ``run_all_tests.sh`` script & several smaller patches..

tastypie/resources.py

+38-10
Original file line numberDiff line numberDiff line change
@@ -1749,8 +1749,11 @@ def get_multiple(self, request, **kwargs):
17491749
Returns a serialized list of resources based on the identifiers
17501750
from the URL.
17511751
1752-
Calls ``obj_get`` to fetch only the objects requested. This method
1753-
only responds to HTTP GET.
1752+
Calls ``obj_get_list`` to fetch only the objects requests in
1753+
a single query. This method only responds to HTTP GET.
1754+
1755+
For backward compatibility the method ``obj_get`` is used if
1756+
``obj_get_list`` is not implemented.
17541757
17551758
Should return a HttpResponse (200 OK).
17561759
"""
@@ -1765,14 +1768,39 @@ def get_multiple(self, request, **kwargs):
17651768
not_found = []
17661769
base_bundle = self.build_bundle(request=request)
17671770

1768-
for identifier in obj_identifiers:
1769-
try:
1770-
obj = self.obj_get(bundle=base_bundle, **{self._meta.detail_uri_name: identifier})
1771-
bundle = self.build_bundle(obj=obj, request=request)
1772-
bundle = self.full_dehydrate(bundle, for_list=True)
1773-
objects.append(bundle)
1774-
except (ObjectDoesNotExist, Unauthorized):
1775-
not_found.append(identifier)
1771+
# We will try to get a queryset from obj_get_list.
1772+
queryset = None
1773+
1774+
try:
1775+
queryset = self.obj_get_list(bundle=base_bundle).filter(
1776+
**{self._meta.detail_uri_name + '__in': obj_identifiers})
1777+
except NotImplementedError:
1778+
pass
1779+
1780+
if queryset is not None:
1781+
# Fetch the objects from the queryset to a dictionary.
1782+
objects_dict = {}
1783+
for obj in queryset:
1784+
objects_dict[str(getattr(obj, self._meta.detail_uri_name))] = obj
1785+
1786+
# Walk the list of identifiers in order and get the objects or feed the not_found list.
1787+
for identifier in obj_identifiers:
1788+
if identifier in objects_dict:
1789+
bundle = self.build_bundle(obj=objects_dict[identifier], request=request)
1790+
bundle = self.full_dehydrate(bundle, for_list=True)
1791+
objects.append(bundle)
1792+
else:
1793+
not_found.append(identifier)
1794+
else:
1795+
# Use the old way.
1796+
for identifier in obj_identifiers:
1797+
try:
1798+
obj = self.obj_get(bundle=base_bundle, **{self._meta.detail_uri_name: identifier})
1799+
bundle = self.build_bundle(obj=obj, request=request)
1800+
bundle = self.full_dehydrate(bundle, for_list=True)
1801+
objects.append(bundle)
1802+
except (ObjectDoesNotExist, Unauthorized):
1803+
not_found.append(identifier)
17761804

17771805
object_list = {
17781806
self._meta.collection_name: objects,

tests/core/tests/resources.py

+24
Original file line numberDiff line numberDiff line change
@@ -1437,6 +1437,7 @@ class ModelResourceTestCase(TestCase):
14371437
fixtures = ['note_testdata.json']
14381438

14391439
def setUp(self):
1440+
self.maxDiff = None
14401441
super(ModelResourceTestCase, self).setUp()
14411442
self.note_1 = Note.objects.get(pk=1)
14421443
self.subject_1 = Subject.objects.create(
@@ -3920,6 +3921,29 @@ class CustomRelatedNoteResource(RelatedNoteResource):
39203921

39213922
self.assertEqual(schema['fields'], expected_schema['fields'])
39223923

3924+
@patch('tastypie.resources.ModelResource.obj_get_list', side_effect=NotImplementedError)
3925+
def test_get_multiple_without_queryset(self, obj_get_list_mock):
3926+
resource = NoteResource()
3927+
request = HttpRequest()
3928+
request.GET = {'format': 'json'}
3929+
request.method = 'GET'
3930+
3931+
resp = resource.get_multiple(request, pk_list='1')
3932+
self.assertEqual(resp.status_code, 200)
3933+
self.assertEqual(resp.content.decode('utf-8'), '{"objects": [{"content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": 1, "is_active": true, "resource_uri": "/api/v1/notes/1/", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00"}]}')
3934+
3935+
resp = resource.get_multiple(request, pk_list='1;2')
3936+
self.assertEqual(resp.status_code, 200)
3937+
self.assertEqual(resp.content.decode('utf-8'), '{"objects": [{"content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": 1, "is_active": true, "resource_uri": "/api/v1/notes/1/", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00"}, {"content": "The dog ate my cat today. He looks seriously uncomfortable.", "created": "2010-03-31T20:05:00", "id": 2, "is_active": true, "resource_uri": "/api/v1/notes/2/", "slug": "another-post", "title": "Another Post", "updated": "2010-03-31T20:05:00"}]}')
3938+
3939+
resp = resource.get_multiple(request, pk_list='2;3')
3940+
self.assertEqual(resp.status_code, 200)
3941+
self.assertEqual(resp.content.decode('utf-8'), '{"not_found": ["3"], "objects": [{"content": "The dog ate my cat today. He looks seriously uncomfortable.", "created": "2010-03-31T20:05:00", "id": 2, "is_active": true, "resource_uri": "/api/v1/notes/2/", "slug": "another-post", "title": "Another Post", "updated": "2010-03-31T20:05:00"}]}')
3942+
3943+
resp = resource.get_multiple(request, pk_list='1;2;4;6')
3944+
self.assertEqual(resp.status_code, 200)
3945+
self.assertEqual(resp.content.decode('utf-8'), '{"objects": [{"content": "This is my very first post using my shiny new API. Pretty sweet, huh?", "created": "2010-03-30T20:05:00", "id": 1, "is_active": true, "resource_uri": "/api/v1/notes/1/", "slug": "first-post", "title": "First Post!", "updated": "2010-03-30T20:05:00"}, {"content": "The dog ate my cat today. He looks seriously uncomfortable.", "created": "2010-03-31T20:05:00", "id": 2, "is_active": true, "resource_uri": "/api/v1/notes/2/", "slug": "another-post", "title": "Another Post", "updated": "2010-03-31T20:05:00"}, {"content": "My neighborhood\'s been kinda weird lately, especially after the lava flow took out the corner store. Granny can hardly outrun the magma with her walker.", "created": "2010-04-01T20:05:00", "id": 4, "is_active": true, "resource_uri": "/api/v1/notes/4/", "slug": "recent-volcanic-activity", "title": "Recent Volcanic Activity.", "updated": "2010-04-01T20:05:00"}, {"content": "Man, the second eruption came on fast. Granny didn\'t have a chance. On the upshot, I was able to save her walker and I got a cool shawl out of the deal!", "created": "2010-04-02T10:05:00", "id": 6, "is_active": true, "resource_uri": "/api/v1/notes/6/", "slug": "grannys-gone", "title": "Granny\'s Gone", "updated": "2010-04-02T10:05:00"}]}')
3946+
39233947
def test_get_multiple(self):
39243948
resource = NoteResource()
39253949
request = HttpRequest()

0 commit comments

Comments
 (0)