Skip to content
This repository has been archived by the owner on Jan 19, 2022. It is now read-only.

Commit

Permalink
speedy api version
Browse files Browse the repository at this point in the history
  • Loading branch information
peterbe committed Jun 20, 2014
1 parent fb6557e commit 3072f31
Show file tree
Hide file tree
Showing 5 changed files with 356 additions and 1 deletion.
95 changes: 95 additions & 0 deletions moztrap/view/api/speedy.py
@@ -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"
)
3 changes: 3 additions & 0 deletions moztrap/view/api/urls.py
Expand Up @@ -37,4 +37,7 @@
urlpatterns = patterns(
"moztrap.view.api",
url(r"", include(v1_api.urls)),
url(r"^speedy/caseselection/",
"speedy.caseselection",
name="caseselection"),
)
3 changes: 2 additions & 1 deletion static/js/init.js
Expand Up @@ -192,7 +192,8 @@ var MT = (function (MT, $) {
MT.populateMultiselectItems({
container: '#suite-edit-form, #suite-add-form',
trigger_field: '#id_product',
ajax_url_root: "/api/v1/caseselection/?format=json&limit=0",
// ajax_url_root: "/api/v1/caseselection/?format=json&limit=0",
ajax_url_root: "/api/speedy/caseselection/?format=json&limit=0",
ajax_trigger_filter: "productversion__product",
ajax_for_field: "case__suites",
for_type: "suite",
Expand Down
Empty file added tests/view/api/__init__.py
Empty file.
256 changes: 256 additions & 0 deletions tests/view/api/test_views.py
@@ -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
)

0 comments on commit 3072f31

Please sign in to comment.