This repository has been archived by the owner on Jan 19, 2022. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
356 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
# Some endpoints in the API need to be really really fast such that we | ||
# are ready to forego the flexibility of the Tastypie API and just write | ||
# our own view functions straight up | ||
|
||
import json | ||
from collections import defaultdict | ||
|
||
from django.http import HttpResponse, HttpResponseBadRequest | ||
|
||
from moztrap.model.library.models import CaseVersion | ||
|
||
|
||
def caseselection(request): | ||
"""This is a speedy version of existing Tastypie API functionality | ||
with the major difference in that it's much much dumber but also | ||
much much faster. | ||
This view function is about 10 faster on an average workload compared | ||
to the Tastpie version but it also has much fewer filtering options | ||
and it returns less data. | ||
""" | ||
if not request.GET.get("productversion__product"): | ||
return HttpResponseBadRequest("productversion__product is required") | ||
product_id = request.GET["productversion__product"] | ||
not_in_case = request.GET.get("case__suites__ne", None) | ||
in_case = request.GET.get("case__suites", None) | ||
order_by = request.GET.get("order_by", "case__id") | ||
limit = int(request.GET.get("limit", 0)) | ||
offset = int(request.GET.get("offset", 0)) | ||
|
||
caseversions = ( | ||
CaseVersion.objects | ||
.filter(latest=True) | ||
.filter(productversion__product_id=product_id) | ||
.select_related("case", "created_by") | ||
.order_by(order_by) | ||
) | ||
if not_in_case: | ||
caseversions = caseversions.exclude(case__suites=not_in_case) | ||
elif in_case: | ||
caseversions = caseversions.filter(case__suites=in_case) | ||
|
||
count = caseversions.count() | ||
if limit: | ||
caseversions = caseversions[offset:limit + offset] | ||
meta = { | ||
"count": count, | ||
"limit": limit, | ||
"offset": offset | ||
} | ||
# Because CaseVersion.tags is a ManyToMany field the only way to | ||
# effectively include those in the CaseVersion query is to use a | ||
# prefetch_related(). | ||
# However, what that does is that it makes a list of tag ids and | ||
# the rather naively re-maps that back into the queryset. | ||
# Instead we're going to go through the mapping table | ||
# (CaseVersion.tags.through) and make a dict that maps each | ||
# caseversion ID to a list of tag IDs. | ||
tags = ( | ||
CaseVersion.tags.through.objects | ||
.filter(tag__deleted_on__isnull=True) | ||
.filter(caseversion__latest=True) | ||
.filter(caseversion__productversion__product_id=product_id) | ||
.select_related("tags") | ||
) | ||
tags_map = defaultdict(list) | ||
for t in tags.values("caseversion_id", "tag__name", "tag__description"): | ||
tags_map[t["caseversion_id"]].append({ | ||
"name": t["tag__name"], | ||
"description": t["tag__description"], | ||
}) | ||
|
||
objects = [] | ||
for each in caseversions: | ||
item = { | ||
"id": unicode(each.id), | ||
"case_id": unicode(each.case_id), | ||
"name": each.name, | ||
"priority": unicode(each.case.priority), | ||
"created_by": {}, | ||
"tags": [], | ||
} | ||
if each.created_by: | ||
item["created_by"] = { | ||
"id": unicode(each.created_by.id), | ||
"username": unicode(each.created_by.username) | ||
} | ||
item["tags"] = tags_map[each.id] | ||
objects.append(item) | ||
|
||
context = {"objects": objects, "meta": meta} | ||
return HttpResponse( | ||
json.dumps(context, indent=2), | ||
content_type="application/json" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,256 @@ | ||
""" | ||
Tests for home speedy API views. | ||
""" | ||
import json | ||
|
||
from django.core.urlresolvers import reverse | ||
|
||
from tests import case | ||
|
||
|
||
class SpeedyCaseVersionsViewTest(case.view.ViewTestCase): | ||
"""Tests for speedy caseversions API view.""" | ||
|
||
@property | ||
def url(self): | ||
return reverse("caseselection") | ||
|
||
def test_that_productversion__product_is_required(self): | ||
res = self.get(status=400) | ||
self.assertEqual(res.content, "productversion__product is required") | ||
|
||
def test_no_results(self): | ||
pv = self.F.ProductVersionFactory.create() | ||
res = self.get( | ||
params={"productversion__product": pv.product.id}, | ||
status=200 | ||
) | ||
self.assertEqual( | ||
json.loads(res.content), | ||
{ | ||
"objects": [], | ||
"meta": { | ||
"count": 0, | ||
"limit": 0, | ||
"offset": 0 | ||
} | ||
} | ||
) | ||
|
||
def test_one_result(self): | ||
pv = self.F.ProductVersionFactory.create() | ||
tc = self.F.CaseFactory.create(product=pv.product) | ||
cv = self.F.CaseVersionFactory.create( | ||
case=tc, productversion=pv, status="active" | ||
) | ||
res = self.get( | ||
params={"productversion__product": pv.product.id}, | ||
status=200 | ||
) | ||
expect = { | ||
"objects": [{ | ||
"id": unicode(cv.id), | ||
"case_id": unicode(tc.id), | ||
"created_by": {}, | ||
"name": cv.name, | ||
"priority": unicode(None), | ||
"tags": [] | ||
}], | ||
"meta": { | ||
"count": 1, | ||
"limit": 0, | ||
"offset": 0 | ||
} | ||
} | ||
self.assertEqual( | ||
json.loads(res.content), | ||
expect | ||
) | ||
|
||
def test_one_fuller_result(self): | ||
pv = self.F.ProductVersionFactory.create() | ||
tc = self.F.CaseFactory.create(product=pv.product) | ||
u = self.F.UserFactory.create() | ||
cv = self.F.CaseVersionFactory.create( | ||
case=tc, productversion=pv, status="active", user=u | ||
) | ||
t = self.F.TagFactory.create() | ||
cv.tags.add(t) | ||
# add a tag too that is deleted | ||
t2 = self.F.TagFactory.create(name="Will Delete") | ||
t2.delete() | ||
assert self.refresh(t2).deleted_on | ||
cv.tags.add(t2) | ||
|
||
res = self.get( | ||
params={"productversion__product": pv.product.id}, | ||
status=200 | ||
) | ||
expect = { | ||
"objects": [{ | ||
"id": unicode(cv.id), | ||
"case_id": unicode(tc.id), | ||
"created_by": { | ||
"id": unicode(u.id), | ||
"username": u.username | ||
}, | ||
"name": cv.name, | ||
"priority": unicode(None), | ||
"tags": [{ | ||
"name": t.name, | ||
"description": t.description | ||
}] | ||
}], | ||
"meta": { | ||
"count": 1, | ||
"limit": 0, | ||
"offset": 0 | ||
} | ||
} | ||
self.assertEqual( | ||
json.loads(res.content), | ||
expect | ||
) | ||
|
||
def test_paginate_results(self): | ||
pv = self.F.ProductVersionFactory.create() | ||
|
||
for i in range(10): | ||
tc = self.F.CaseFactory.create(product=pv.product) | ||
self.F.CaseVersionFactory.create( | ||
case=tc, productversion=pv, status="active" | ||
) | ||
|
||
res = self.get( | ||
params={"productversion__product": pv.product.id}, | ||
status=200 | ||
) | ||
expect_meta = { | ||
"count": 10, | ||
"limit": 0, | ||
"offset": 0 | ||
} | ||
self.assertEqual( | ||
json.loads(res.content)['meta'], | ||
expect_meta | ||
) | ||
self.assertEqual( | ||
len(json.loads(res.content)["objects"]), | ||
10 | ||
) | ||
|
||
# this time set a limit | ||
res = self.get( | ||
params={ | ||
"productversion__product": pv.product.id, | ||
"limit": 3 | ||
}, | ||
status=200 | ||
) | ||
expect_meta = { | ||
"count": 10, | ||
"limit": 3, | ||
"offset": 0 | ||
} | ||
self.assertEqual( | ||
json.loads(res.content)['meta'], | ||
expect_meta | ||
) | ||
self.assertEqual( | ||
len(json.loads(res.content)["objects"]), | ||
3 | ||
) | ||
|
||
def test_filtered_by_suites(self): | ||
pv = self.F.ProductVersionFactory.create() | ||
tc1 = self.F.CaseFactory.create(product=pv.product) | ||
cv1 = self.F.CaseVersionFactory.create( | ||
case=tc1, productversion=pv, status="active", name="Foo" | ||
) | ||
tc2 = self.F.CaseFactory.create(product=pv.product) | ||
cv2 = self.F.CaseVersionFactory.create( | ||
case=tc2, productversion=pv, status="active", name="Bar" | ||
) | ||
assert cv1.name != cv2.name | ||
suite = self.F.SuiteFactory.create(product=pv.product, status="active") | ||
self.F.SuiteCaseFactory.create(suite=suite, case=tc1) | ||
|
||
suite2 = self.F.SuiteFactory.create( | ||
product=pv.product, status="active" | ||
) | ||
self.F.SuiteCaseFactory.create(suite=suite2, case=tc2) | ||
|
||
res = self.get( | ||
params={ | ||
"productversion__product": pv.product.id, | ||
"case__suites": suite.id}, | ||
status=200 | ||
) | ||
expect_names = [cv1.name] | ||
self.assertEqual( | ||
[x["name"] for x in json.loads(res.content)["objects"]], | ||
expect_names | ||
) | ||
res = self.get( | ||
params={ | ||
"productversion__product": pv.product.id, | ||
"case__suites__ne": suite.id}, | ||
status=200 | ||
) | ||
expect_names = [cv2.name] | ||
self.assertEqual( | ||
[x["name"] for x in json.loads(res.content)["objects"]], | ||
expect_names | ||
) | ||
|
||
def test_order_by_suites_order(self): | ||
pv = self.F.ProductVersionFactory.create() | ||
tc1 = self.F.CaseFactory.create(product=pv.product) | ||
cv1 = self.F.CaseVersionFactory.create( | ||
case=tc1, productversion=pv, status="active", name="Foo" | ||
) | ||
tc2 = self.F.CaseFactory.create(product=pv.product) | ||
cv2 = self.F.CaseVersionFactory.create( | ||
case=tc2, productversion=pv, status="active", name="Bar" | ||
) | ||
assert cv1.name != cv2.name | ||
suite = self.F.SuiteFactory.create(product=pv.product, status="active") | ||
suitecase = self.F.SuiteCaseFactory.create( | ||
suite=suite, case=tc1, order=3 | ||
) | ||
|
||
suite2 = self.F.SuiteFactory.create( | ||
product=pv.product, status="active" | ||
) | ||
suitecase2 = self.F.SuiteCaseFactory.create( | ||
suite=suite2, case=tc2, order=2 | ||
) | ||
|
||
res = self.get( | ||
params={ | ||
"productversion__product": pv.product.id, | ||
"order_by": "case__suitecases__order"}, | ||
status=200 | ||
) | ||
# becase order=2 < order=1 | ||
expect_names = [cv2.name, cv1.name] | ||
self.assertEqual( | ||
[x["name"] for x in json.loads(res.content)["objects"]], | ||
expect_names | ||
) | ||
suitecase.order = 0 | ||
suitecase.save() | ||
|
||
res = self.get( | ||
params={ | ||
"productversion__product": pv.product.id, | ||
"order_by": "case__suitecases__order"}, | ||
status=200 | ||
) | ||
# becase order=2 > order=0 | ||
expect_names = [cv1.name, cv2.name] | ||
self.assertEqual( | ||
[x["name"] for x in json.loads(res.content)["objects"]], | ||
expect_names | ||
) |