Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Notes alpha #4

Merged
merged 21 commits into from
Nov 30, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions api/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django import forms


class NoteForm(forms.Form):
note = forms.CharField(min_length=1)

def save(self, parsed_instance):
parsed_instance.add_note(self.cleaned_data['note'])
parsed_instance.save()
7 changes: 6 additions & 1 deletion api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from main.models import UserProfile
from main.forms import UserProfileForm, RegistrationFormUserProfile

from odk_logger.models import XForm
from odk_logger.models import XForm, Note

from api.models import Project, OrganizationProfile, Team
from api.fields import HyperlinkedMultiIdentityField,\
Expand Down Expand Up @@ -242,3 +242,8 @@ def restore_object(self, attrs, instance=None):
self.errors['name'] = u'A team name is required'
return attrs
return Team(organization=org, name=team_name)


class NoteSerializer(serializers.ModelSerializer):
class Meta:
model = Note
41 changes: 39 additions & 2 deletions api/tests/test_data_api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.test import RequestFactory
from main.tests.test_base import MainTestCase

from api.views import DataViewSet, XFormViewSet
from odk_logger.models import Note
from api.views import DataViewSet, XFormViewSet, NoteViewSet


class TestDataAPI(MainTestCase):
Expand Down Expand Up @@ -83,3 +83,40 @@ def test_add_form_tag_propagates_to_data_tags(self):
self.assertEqual(response.data, [])
for i in self.xform.surveys.all():
self.assertNotIn(u'hello', i.tags.names())

def test_add_notes_to_data_point(self):
# add a note to a specific data point
view = NoteViewSet.as_view({
'get': 'retrieve',
'post': 'create',
})
note = {'note': u"Road Warrior"}
dataid = self.xform.surveys.all()[0].pk
note['instance'] = dataid
request = self.factory.post('/', data=note, **self.extra)
self.assertTrue(self.xform.surveys.count())
response = view(request)
self.assertEqual(response.status_code, 201)
pk = response.data['id']
request = self.factory.get('/', **self.extra)
response = view(request, pk=pk)
self.assertEqual(response.status_code, 200)
self.assertDictContainsSubset(note, response.data)
view = NoteViewSet.as_view({
'get': 'list',
'delete': 'destroy'
})
user = self._create_user('deno', 'deno')
extra = {
'HTTP_AUTHORIZATION': 'Token %s' % user.auth_token}
request = self.factory.get('/', **extra)
response = view(request)
self.assertEqual(response.status_code, 200)
self.assertEquals(response.data, [])
request = self.factory.delete('/', **self.extra)
response = view(request, pk=pk)
self.assertEqual(response.status_code, 204)
request = self.factory.get('/', **self.extra)
response = view(request)
self.assertEqual(response.status_code, 200)
self.assertEquals(response.data, [])
1 change: 1 addition & 0 deletions api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ def get_urls(self):
router.register(r'forms', api_views.XFormViewSet)
router.register(r'projects', api_views.ProjectViewSet)
router.register(r'teams', api_views.TeamViewSet)
router.register(r'notes', api_views.NoteViewSet)
router.register(r'data', api_views.DataViewSet, base_name='data')
router.register(r'stats/submissions',
api_views.StatsViewSet, base_name='stats')
71 changes: 68 additions & 3 deletions api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@
check_and_set_form_by_id_string
from main.models import UserProfile

from odk_logger.models import XForm, Instance
from odk_logger.models import XForm, Instance, Note
from odk_viewer.models import ParsedInstance

from api.models import Project, OrganizationProfile, ProjectXForm, Team
from api.forms import NoteForm


class UserViewSet(viewsets.ReadOnlyModelViewSet):
Expand Down Expand Up @@ -1031,6 +1032,7 @@ class DataViewSet(viewsets.ViewSet):
> Response
>
> HTTP 200 OK

"""
permission_classes = [permissions.IsAuthenticated, ]
lookup_field = 'owner'
Expand Down Expand Up @@ -1087,7 +1089,11 @@ def list(self, request, owner=None, formid=None, dataid=None, **kwargs):
if not formid and not dataid and not tags:
data = self._get_formlist_data_points(request, owner)
if formid:
xform = check_and_set_form_by_id(int(formid), request)
xform = None
try:
xform = check_and_set_form_by_id(int(formid), request)
except ValueError:
xform = check_and_set_form_by_id_string(formid, request)
if not xform:
raise exceptions.PermissionDenied(
_("You do not have permission to "
Expand Down Expand Up @@ -1125,7 +1131,11 @@ class TagForm(forms.Form):
tags = TagField()
if owner is None and not request.user.is_anonymous():
owner = request.user.username
xform = check_and_set_form_by_id(int(formid), request)
xform = None
try:
xform = check_and_set_form_by_id(int(formid), request)
except ValueError:
xform = check_and_set_form_by_id_string(formid, request)
if not xform:
raise exceptions.PermissionDenied(
_("You do not have permission to "
Expand Down Expand Up @@ -1160,6 +1170,61 @@ class TagForm(forms.Form):
return Response(data, status=status)


class NoteViewSet(viewsets.ModelViewSet):
"""## Add Notes to a submission

A `POST` payload of parameters:

`note` - the note string to add to a data point
`instance` - the data point id

<pre class="prettyprint">
<b>POST</b> /api/v1/notes</pre>

Payload

{"instance": 1, "note": "This is a note."}

> Response
>
> {
> "id": 1,
> "instance": 1,
> "note": "This is a note."
> ...
> }
>
> HTTP 201 OK

# Get List of notes for a data point

A `GET` request will return the list of notes applied to a data point.

<pre class="prettyprint">
<b>GET</b> /api/v1/notes</pre>


> Response
>
> [{
> "id": 1,
> "instance": 1,
> "note": "This is a note."
> ...
> }, ...]
>
>
> HTTP 200 OK
"""
queryset = Note.objects.all()
serializer_class = api_serializers.NoteSerializer
permission_classes = [permissions.DjangoModelPermissions,
permissions.IsAuthenticated, ]

def get_queryset(self):
return Note.objects.filter(instance__xform__user=self.request.user)


class StatsViewSet(viewsets.ViewSet):
"""
Provides submissions counts grouped by a specified field.
Expand Down
2 changes: 2 additions & 0 deletions common_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@

# hold tags
TAGS = u"_tags"

NOTES = u"_notes"
4 changes: 2 additions & 2 deletions main/tests/fixtures/csv_export/export.csv
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
bed_net[1]/member[1]/name,bed_net[1]/member[2]/name,bed_net[2]/member[1]/name,meta/instanceID,_uuid,_submission_time
Andrew,Bob,Carl,uuid:4e274a99-f7a9-467c-bb44-9f9a8ceee9a7,4e274a99-f7a9-467c-bb44-9f9a8ceee9a7,2013-02-18T15:54:01
bed_net[1]/member[1]/name,bed_net[1]/member[2]/name,bed_net[2]/member[1]/name,meta/instanceID,_uuid,_submission_time,_tags,_notes
Andrew,Bob,Carl,uuid:4e274a99-f7a9-467c-bb44-9f9a8ceee9a7,4e274a99-f7a9-467c-bb44-9f9a8ceee9a7,2013-02-18T15:54:01,,
4 changes: 2 additions & 2 deletions main/tests/fixtures/csv_export/tutorial_w_repeats.csv
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
name,age,picture,has_children,children[1]/childs_age,children[1]/childs_name,children[2]/childs_age,children[2]/childs_name,gps,_gps_latitude,_gps_longitude,_gps_altitude,_gps_precision,web_browsers/firefox,web_browsers/chrome,web_browsers/ie,web_browsers/safari,meta/instanceID,_uuid,_submission_time
Bob,25,n/a,1,12,Tom,5,Dick,-1.2625621 36.7921711 0.0 20.0,-1.2625621,36.7921711,0.0,20.0,n/a,n/a,n/a,n/a,uuid:b31c6ac2-b8ca-4180-914f-c844fa10ed3b,b31c6ac2-b8ca-4180-914f-c844fa10ed3b,2013-02-18T15:54:01
name,age,picture,has_children,children[1]/childs_age,children[1]/childs_name,children[2]/childs_age,children[2]/childs_name,gps,_gps_latitude,_gps_longitude,_gps_altitude,_gps_precision,web_browsers/firefox,web_browsers/chrome,web_browsers/ie,web_browsers/safari,meta/instanceID,_uuid,_submission_time,_tags,_notes
Bob,25,n/a,1,12,Tom,5,Dick,-1.2625621 36.7921711 0.0 20.0,-1.2625621,36.7921711,0.0,20.0,n/a,n/a,n/a,n/a,uuid:b31c6ac2-b8ca-4180-914f-c844fa10ed3b,b31c6ac2-b8ca-4180-914f-c844fa10ed3b,2013-02-18T15:54:01,,
42 changes: 22 additions & 20 deletions main/tests/fixtures/transportation/headers.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
[
"transport/available_transportation_types_to_referral_facility/ambulance",
"transport/available_transportation_types_to_referral_facility/bicycle",
"transport/available_transportation_types_to_referral_facility/boat_canoe",
"transport/available_transportation_types_to_referral_facility/bus",
"transport/available_transportation_types_to_referral_facility/donkey_mule_cart",
"transport/available_transportation_types_to_referral_facility/keke_pepe",
"transport/available_transportation_types_to_referral_facility/lorry",
"transport/available_transportation_types_to_referral_facility/motorbike",
"transport/available_transportation_types_to_referral_facility/taxi",
"transport/available_transportation_types_to_referral_facility/other",
"transport/available_transportation_types_to_referral_facility_other",
"transport/loop_over_transport_types_frequency/ambulance/frequency_to_referral_facility",
"transport/loop_over_transport_types_frequency/bicycle/frequency_to_referral_facility",
"transport/loop_over_transport_types_frequency/boat_canoe/frequency_to_referral_facility",
"transport/loop_over_transport_types_frequency/bus/frequency_to_referral_facility",
"transport/loop_over_transport_types_frequency/donkey_mule_cart/frequency_to_referral_facility",
"transport/loop_over_transport_types_frequency/keke_pepe/frequency_to_referral_facility",
"transport/loop_over_transport_types_frequency/lorry/frequency_to_referral_facility",
"transport/loop_over_transport_types_frequency/motorbike/frequency_to_referral_facility",
"transport/available_transportation_types_to_referral_facility/ambulance",
"transport/available_transportation_types_to_referral_facility/bicycle",
"transport/available_transportation_types_to_referral_facility/boat_canoe",
"transport/available_transportation_types_to_referral_facility/bus",
"transport/available_transportation_types_to_referral_facility/donkey_mule_cart",
"transport/available_transportation_types_to_referral_facility/keke_pepe",
"transport/available_transportation_types_to_referral_facility/lorry",
"transport/available_transportation_types_to_referral_facility/motorbike",
"transport/available_transportation_types_to_referral_facility/taxi",
"transport/available_transportation_types_to_referral_facility/other",
"transport/available_transportation_types_to_referral_facility_other",
"transport/loop_over_transport_types_frequency/ambulance/frequency_to_referral_facility",
"transport/loop_over_transport_types_frequency/bicycle/frequency_to_referral_facility",
"transport/loop_over_transport_types_frequency/boat_canoe/frequency_to_referral_facility",
"transport/loop_over_transport_types_frequency/bus/frequency_to_referral_facility",
"transport/loop_over_transport_types_frequency/donkey_mule_cart/frequency_to_referral_facility",
"transport/loop_over_transport_types_frequency/keke_pepe/frequency_to_referral_facility",
"transport/loop_over_transport_types_frequency/lorry/frequency_to_referral_facility",
"transport/loop_over_transport_types_frequency/motorbike/frequency_to_referral_facility",
"transport/loop_over_transport_types_frequency/taxi/frequency_to_referral_facility",
"meta/instanceID",
"_uuid",
"_submission_time"
"_submission_time",
"_tags",
"_notes"
]
Binary file modified main/tests/fixtures/transportation/transportation_export.xls
Binary file not shown.
4 changes: 2 additions & 2 deletions main/tests/fixtures/userone/userone_with_dot_name_fields.csv
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
Q1.1,Q1.2,Q1.3,Q1.4,_Q1.4_latitude,_Q1.4_longitude,_Q1.4_altitude,_Q1.4_precision,rQ6.4[1]/Q6.4,meta/instanceID,_uuid,_submission_time
Office,4,n/a,-1.2624975 36.7923384 0.0 25.0,-1.2624975,36.7923384,0.0,25.0,Cool,uuid:a32f232c-77cb-4468-b55b-6495d5e5de7,a32f232c-77cb-4468-b55b-6495d5e5de7,2013-02-18T15:54:01
Q1.1,Q1.2,Q1.3,Q1.4,_Q1.4_latitude,_Q1.4_longitude,_Q1.4_altitude,_Q1.4_precision,rQ6.4[1]/Q6.4,meta/instanceID,_uuid,_submission_time,_tags,_notes
Office,4,n/a,-1.2624975 36.7923384 0.0 25.0,-1.2624975,36.7923384,0.0,25.0,Cool,uuid:a32f232c-77cb-4468-b55b-6495d5e5de7,a32f232c-77cb-4468-b55b-6495d5e5de7,2013-02-18T15:54:01,,
12 changes: 8 additions & 4 deletions main/tests/test_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,8 @@ def _check_csv_export_second_pass(self):
data = [
{'meta/instanceID': 'uuid:5b2cc313-fc09-437e-8149-fcd32f695d41',
'_uuid': '5b2cc313-fc09-437e-8149-fcd32f695d41',
'_submission_time': '2013-02-14T15:37:21'
'_submission_time': '2013-02-14T15:37:21',
'_tags': '', '_notes': ''
},
{"available_transportation_types_to_referral_facility/ambulance":
"True",
Expand All @@ -355,14 +356,16 @@ def _check_csv_export_second_pass(self):
"loop_over_transport_types_frequency/bicycle/frequency_to_referral_facility": "weekly",
"meta/instanceID": "uuid:f3d8dc65-91a6-4d0f-9e97-802128083390",
'_uuid': 'f3d8dc65-91a6-4d0f-9e97-802128083390',
'_submission_time': '2013-02-14T15:37:22'
'_submission_time': '2013-02-14T15:37:22',
'_tags': '', '_notes': ''
},
{"available_transportation_types_to_referral_facility/ambulance":
"True",
"loop_over_transport_types_frequency/ambulance/frequency_to_referral_facility": "weekly",
"meta/instanceID": "uuid:9c6f3468-cfda-46e8-84c1-75458e72805d",
'_uuid': '9c6f3468-cfda-46e8-84c1-75458e72805d',
'_submission_time': '2013-02-14T15:37:23'
'_submission_time': '2013-02-14T15:37:23',
'_tags': '', '_notes': ''
},
{"available_transportation_types_to_referral_facility/taxi":
"True",
Expand All @@ -373,7 +376,8 @@ def _check_csv_export_second_pass(self):
"loop_over_transport_types_frequency/taxi/frequency_to_referral_facility": "daily",
"meta/instanceID": "uuid:9f0a1508-c3b7-4c99-be00-9b237c26bcbf",
'_uuid': '9f0a1508-c3b7-4c99-be00-9b237c26bcbf',
'_submission_time': '2013-02-14T15:37:24'
'_submission_time': '2013-02-14T15:37:24',
'_tags': '', '_notes': ''
}
]

Expand Down
Loading