Skip to content

Commit

Permalink
Merge pull request #523 from 18F/cursor-api
Browse files Browse the repository at this point in the history
Add endpoint for serializing trees
  • Loading branch information
cmc333333 authored Oct 5, 2017
2 parents 692c936 + bc53f6d commit 2e7a4a9
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 1 deletion.
2 changes: 1 addition & 1 deletion api/document/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def subtree(self, queryset=None):
if queryset is None:
queryset = self.__class__.objects
descendant_models = queryset.filter(
left__gt=self.left, right__lt=self.right, policy=self.policy
left__gt=self.left, right__lt=self.right, policy_id=self.policy_id
).order_by('left')

tree = DiGraph()
Expand Down
23 changes: 23 additions & 0 deletions api/document/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from rest_framework import serializers

from document.models import DocNode


class DocCursorSerializer(serializers.ModelSerializer):
children = serializers.SerializerMethodField()

class Meta:
model = DocNode
fields = ('identifier', 'node_type', 'type_emblem', 'text', 'depth',
'children')

def to_representation(self, instance):
"""We want to serialize the wrapped model, not the cursor. However, we
need to hang on to that cursor for rendering our children."""
self.context['cursor'] = instance
return super().to_representation(instance.model)

def get_children(self, instance):
return self.__class__(
self.context['cursor'].children(), many=True
).data
65 changes: 65 additions & 0 deletions api/document/tests/serializers_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from document import serializers
from document.models import DocNode


def test_end_to_end():
"""Create a tree, then serialize it."""
root = DocNode.new_tree('root', '0')
root.add_child('sect', text='Section 1')
sect2 = root.add_child('sect')
pa = sect2.add_child('par', 'a')
pa.add_child('par', '1', text='Paragraph (a)(1)')
sect2.add_child('par', 'b')

result = serializers.DocCursorSerializer(root).data
assert result == {
'identifier': 'root_0',
'node_type': 'root',
'type_emblem': '0',
'text': '',
'depth': 0,
'children': [
{
'identifier': 'root_0__sect_1',
'node_type': 'sect',
'type_emblem': '1',
'text': 'Section 1',
'depth': 1,
'children': [],
},
{
'identifier': 'root_0__sect_2',
'node_type': 'sect',
'type_emblem': '2',
'text': '',
'depth': 1,
'children': [
{
'identifier': 'root_0__sect_2__par_a',
'node_type': 'par',
'type_emblem': 'a',
'text': '',
'depth': 2,
'children': [
{
'identifier': 'root_0__sect_2__par_a__par_1',
'node_type': 'par',
'type_emblem': '1',
'text': 'Paragraph (a)(1)',
'depth': 3,
'children': [],
},
],
},
{
'identifier': 'root_0__sect_2__par_b',
'node_type': 'par',
'type_emblem': 'b',
'text': '',
'depth': 2,
'children': [],
},
],
},
],
}
49 changes: 49 additions & 0 deletions api/document/tests/views_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import json

import pytest
from model_mommy import mommy

from document.models import DocNode
from document.serializers import DocCursorSerializer
from reqs.models import Policy


@pytest.mark.django_db
@pytest.mark.urls('document.urls')
def test_404s(client):
policy = mommy.make(Policy)
root = DocNode.new_tree('root', '0', policy=policy)
root.add_child('sect', policy=policy)
root.nested_set_renumber()
DocNode.objects.bulk_create(n.model for n in root.walk())

assert client.get("/000001").status_code == 404
assert client.get(f"/{policy.pk}").status_code == 200
assert client.get(f"/{policy.pk}/root_0").status_code == 200
assert client.get(f"/{policy.pk}/root_1").status_code == 404
assert client.get(f"/{policy.pk}/root_0__sect_1").status_code == 200
assert client.get(f"/{policy.pk}/root_0__sect_2").status_code == 404


@pytest.mark.django_db
@pytest.mark.urls('document.urls')
def test_correct_data(client):
policy = mommy.make(Policy)
root = DocNode.new_tree('root', '0', policy=policy)
sect1 = root.add_child('sect', policy=policy)
root.add_child('sect', policy=policy)
sect1.add_child('par', 'a', policy=policy)
root.nested_set_renumber()
DocNode.objects.bulk_create(n.model for n in root.walk())

def result(url):
return json.loads(client.get(url).content.decode('utf-8'))

assert result(f"/{policy.pk}") == DocCursorSerializer(root).data
assert result(f"/{policy.pk}/root_0") == DocCursorSerializer(root).data
assert result(f"/{policy.pk}/root_0__sect_1") \
== DocCursorSerializer(root['sect_1']).data
assert result(f"/{policy.pk}/root_0__sect_2") \
== DocCursorSerializer(root['sect_2']).data
assert result(f"/{policy.pk}/root_0__sect_1__par_a") \
== DocCursorSerializer(root['sect_1']['par_a']).data
8 changes: 8 additions & 0 deletions api/document/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.conf.urls import url

from document.views import TreeView

urlpatterns = [
url(r'^(?P<policy_id>[0-9]+)(/(?P<identifier>[a-zA-Z0-9_-]+))?',
TreeView.as_view()),
]
19 changes: 19 additions & 0 deletions api/document/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from django.shortcuts import get_object_or_404
from rest_framework.generics import RetrieveAPIView

from document.models import DocNode
from document.serializers import DocCursorSerializer


class TreeView(RetrieveAPIView):
serializer_class = DocCursorSerializer
queryset = DocNode.objects.none() # Used to determine permissions

def get_object(self):
query_args = {'policy_id': self.kwargs['policy_id']}
if self.kwargs.get('identifier'):
query_args['identifier'] = self.kwargs['identifier']
else:
query_args['depth'] = 0
root_struct = get_object_or_404(DocNode, **query_args)
return root_struct.subtree()
1 change: 1 addition & 0 deletions api/omb_eregs/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^admin/', admin_site.urls),
url(r'^document/', include('document.urls')),
]

if settings.DEBUG:
Expand Down

0 comments on commit 2e7a4a9

Please sign in to comment.