def finalize_response(self, request, response, *args, **kwargs): if request.method == 'GET' and response.status_code < 300: if self.last_modified_date is not None: self.headers.update( last_modified_header(self.last_modified_date)) else: obj = None if hasattr(self, 'object_list'): generator_type = isinstance(self.object_list, types.GeneratorType) if isinstance(self.object_list, list) \ and len(self.object_list): obj = self.object_list[len(self.object_list) - 1] elif not isinstance(self.object_list, list) and \ not generator_type: obj = self.object_list.last() if hasattr(self, 'object'): obj = self.object if not obj: obj = self.queryset.last() self.headers.update(last_modified_header(get_date(obj))) return super(LastModifiedMixin, self).finalize_response(request, response, *args, **kwargs)
def finalize_response(self, request, response, *args, **kwargs): if request.method == 'GET' and response.status_code < 300: if self.last_modified_date is not None: self.headers.update( last_modified_header(self.last_modified_date)) else: obj = None if hasattr(self, 'object_list'): generator_type = isinstance(self.object_list, types.GeneratorType) if isinstance(self.object_list, list) \ and len(self.object_list): obj = self.object_list[len(self.object_list) - 1] elif not isinstance(self.object_list, list) and \ not generator_type: obj = self.object_list.last() if hasattr(self, 'object'): obj = self.object if not obj: obj = self.queryset.last() self.headers.update(last_modified_header(get_date(obj))) return super(LastModifiedMixin, self).finalize_response( request, response, *args, **kwargs)
def retrieve(self, request, *args, **kwargs): data_id = str(kwargs.get('dataid')) _format = kwargs.get('format') if not data_id.isdigit(): raise ParseError(_(u"Data ID should be an integer")) try: instance = Instance.objects.get(pk=data_id) self.headers = merge_dicts( self.headers, last_modified_header(get_date(instance, 'modified'))) if _format == 'json' or _format is None: return Response(instance.json) elif _format == 'xml': return Response(instance.xml) else: raise ParseError( _(u"'%(_format)s' format unknown or not implemented!" % {'_format': _format})) except Instance.DoesNotExist: raise ParseError( _(u"data with id '%(data_id)s' not found!" % {'data_id': data_id}))
def forms(self, request, **kwargs): """Add a form to a project or list forms for the project. The request key `xls_file` holds the XLSForm file object. """ project = get_object_or_404(Project, pk=kwargs.get('pk')) self.headers = merge_dicts( self.headers, last_modified_header(get_date(project, 'modified'))) if request.method.upper() == 'POST': survey = utils.publish_project_xform(request, project) if isinstance(survey, XForm): xform = XForm.objects.get(pk=survey.pk) serializer = XFormSerializer( xform, context={'request': request}) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(survey, status=status.HTTP_400_BAD_REQUEST) project_xforms = project.projectxform_set.values('xform') xforms = XForm.objects.filter(pk__in=project_xforms) serializer = XFormSerializer(xforms, context={'request': request}, many=True) return Response(serializer.data)
def retrieve(self, request, *args, **kwargs): data_id = str(kwargs.get('dataid')) _format = kwargs.get('format') if not data_id.isdigit(): raise ParseError(_(u"Data ID should be an integer")) try: instance = Instance.objects.get(pk=data_id) self.headers = merge_dicts( self.headers, last_modified_header(get_date(instance, 'modified'))) if _format == 'json' or _format is None: return Response(instance.json) elif _format == 'xml': return Response(instance.xml) else: raise ParseError( _(u"'%(_format)s' format unknown or not implemented!" % {'_format': _format}) ) except Instance.DoesNotExist: raise ParseError( _(u"data with id '%(data_id)s' not found!" % {'data_id': data_id}) )
def labels(self, request, *args, **kwargs): http_status = status.HTTP_400_BAD_REQUEST instance = self.get_object() self.headers = merge_dicts( self.headers, last_modified_header(get_date(instance, 'modified'))) if request.method == 'POST': if add_tags_to_instance(request, instance): http_status = status.HTTP_201_CREATED tags = instance.tags label = kwargs.get('label', None) if request.method == 'GET' and label: data = [ tag['name'] for tag in tags.filter(name=label).values('name') ] elif request.method == 'DELETE' and label: count = tags.count() tags.remove(label) # Accepted, label does not exist hence nothing removed http_status = status.HTTP_200_OK if count == tags.count() \ else status.HTTP_404_NOT_FOUND data = list(tags.names()) else: data = list(tags.names()) if request.method == 'GET': http_status = status.HTTP_200_OK return Response(data, status=http_status)
def retrieve(self, request, *args, **kwargs): xform = self.get_object() serializer = self.get_serializer(xform) dd = xform.data_dictionary() field_name = request.QUERY_PARAMS.get('field_name') fields = request.QUERY_PARAMS.get('fields') fmt = kwargs.get('format') self.headers = merge_dicts( self.headers, last_modified_header(get_date(xform, 'modified'))) if fields: if fmt is not None and fmt != 'json': raise ParseError("Error: only JSON format supported.") xform = self.get_object() self.headers['Last-Modified'] = get_date(xform, 'modified') context = self.get_serializer_context() serializer = FieldsChartSerializer(instance=xform, context=context) return Response(serializer.data) if field_name: # check if its the special _submission_time META if field_name == common_tags.SUBMISSION_TIME: field = common_tags.SUBMISSION_TIME else: # use specified field to get summary fields = filter(lambda f: f.name == field_name, [e for e in dd.survey_elements]) if len(fields) == 0: raise Http404("Field %s does not not exist on the form" % field_name) field = fields[0] data = build_chart_data_for_field(xform, field) if request.accepted_renderer.format == 'json': xform = xform.pk elif request.accepted_renderer.format == 'html' and 'data' in data: for item in data['data']: if isinstance(item[field_name], list): item[field_name] = u', '.join(item[field_name]) data.update({'xform': xform}) return Response(data, template_name='chart_detail.html') if fmt != 'json' and field_name is None: raise ParseError("Not supported") data = serializer.data data["fields"] = {} for field in dd.survey_elements: field_url = get_form_field_chart_url(data["url"], field.name) data["fields"][field.name] = field_url return Response(data)
def forms(self, request, **kwargs): """Add a form to a project or list forms for the project. The request key `xls_file` holds the XLSForm file object. """ project = get_object_or_404(Project, pk=kwargs.get('pk')) self.headers = merge_dicts( self.headers, last_modified_header(get_date(project, 'modified'))) if request.method.upper() == 'POST': survey = utils.publish_project_xform(request, project) if isinstance(survey, XForm): xform = XForm.objects.get(pk=survey.pk) serializer = XFormSerializer(xform, context={'request': request}) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(survey, status=status.HTTP_400_BAD_REQUEST) project_xforms = project.projectxform_set.values('xform') xforms = XForm.objects.filter(pk__in=project_xforms) serializer = XFormSerializer(xforms, context={'request': request}, many=True) return Response(serializer.data)
def labels(self, request, *args, **kwargs): http_status = status.HTTP_400_BAD_REQUEST instance = self.get_object() self.headers = merge_dicts( self.headers, last_modified_header(get_date(instance, 'modified'))) if request.method == 'POST': if add_tags_to_instance(request, instance): http_status = status.HTTP_201_CREATED tags = instance.tags label = kwargs.get('label', None) if request.method == 'GET' and label: data = [tag['name'] for tag in tags.filter(name=label).values('name')] elif request.method == 'DELETE' and label: count = tags.count() tags.remove(label) # Accepted, label does not exist hence nothing removed http_status = status.HTTP_200_OK if count == tags.count() \ else status.HTTP_404_NOT_FOUND data = list(tags.names()) else: data = list(tags.names()) if request.method == 'GET': http_status = status.HTTP_200_OK return Response(data, status=http_status)
def starred(self, request, *args, **kwargs): """Return projects starred for this user.""" user_profile = self.get_object() self.headers = merge_dicts( self.headers, last_modified_header(get_date(user_profile, 'joined'))) user = user_profile.user projects = user.project_set.all() serializer = ProjectSerializer(projects, context={'request': request}, many=True) return Response(data=serializer.data)
def starred(self, request, *args, **kwargs): """Return projects starred for this user.""" user_profile = self.get_object() self.headers = merge_dicts( self.headers, last_modified_header(get_date(user_profile, 'joined'))) user = user_profile.user projects = user.project_set.all() serializer = ProjectSerializer(projects, context={'request': request}, many=True) return Response(data=serializer.data)
def finalize_response(self, request, response, *args, **kwargs): if request.method == 'GET': obj = None if hasattr(self, 'object_list'): obj = self.object_list.last() if hasattr(self, 'object'): obj = self.object if not obj: obj = self.queryset.last() if response.status_code < 400: self.headers.update(last_modified_header(get_date(obj))) return super(LastModifiedMixin, self).finalize_response( request, response, *args, **kwargs)
def finalize_response(self, request, response, *args, **kwargs): if request.method == 'GET': obj = None if hasattr(self, 'object_list'): obj = self.object_list.last() if hasattr(self, 'object'): obj = self.object if not obj: obj = self.queryset.last() if response.status_code < 400: self.headers.update(last_modified_header(get_date(obj))) return super(LastModifiedMixin, self).finalize_response(request, response, *args, **kwargs)
def star(self, request, *args, **kwargs): user = request.user project = get_object_or_404(Project, pk=kwargs.get('pk')) self.headers = merge_dicts( self.headers, last_modified_header(get_date(project, 'modified'))) if request.method == 'DELETE': project.user_stars.remove(user) elif request.method == 'POST': project.user_stars.add(user) elif request.method == 'GET': users = project.user_stars.values('pk') user_profiles = UserProfile.objects.filter(user__in=users) serializer = UserProfileSerializer(user_profiles, context={'request': request}, many=True) return Response(serializer.data) return Response(status=status.HTTP_204_NO_CONTENT)
def star(self, request, *args, **kwargs): user = request.user project = get_object_or_404(Project, pk=kwargs.get('pk')) self.headers = merge_dicts( self.headers, last_modified_header(get_date(project, 'modified'))) if request.method == 'DELETE': project.user_stars.remove(user) elif request.method == 'POST': project.user_stars.add(user) elif request.method == 'GET': users = project.user_stars.values('pk') user_profiles = UserProfile.objects.filter(user__in=users) serializer = UserProfileSerializer(user_profiles, context={'request': request}, many=True) return Response(serializer.data) return Response(status=status.HTTP_204_NO_CONTENT)
def retrieve(self, request, *args, **kwargs): self.object = self.get_object() self.headers = merge_dicts( self.headers, last_modified_header(get_date(self.object, 'modified'))) if isinstance(request.accepted_renderer, MediaFileRenderer) \ and self.object.media_file is not None: data = self.object.media_file.read() return Response(data, content_type=self.object.mimetype) filename = request.QUERY_PARAMS.get('filename') serializer = self.get_serializer(self.object) if filename: if filename == self.object.media_file.name: return Response(serializer.get_download_url(self.object)) else: raise Http404(_("Filename '%s' not found." % filename)) return Response(serializer.data)
def retrieve(self, request, *args, **kwargs): self.object = self.get_object() self.headers = merge_dicts( self.headers, last_modified_header(get_date(self.object, 'modified'))) if isinstance(request.accepted_renderer, MediaFileRenderer) \ and self.object.media_file is not None: data = self.object.media_file.read() return Response(data, content_type=self.object.mimetype) filename = request.QUERY_PARAMS.get('filename') serializer = self.get_serializer(self.object) if filename: if filename == self.object.media_file.name: return Response(serializer.get_download_url(self.object)) else: raise Http404(_("Filename '%s' not found." % filename)) return Response(serializer.data)
def enketo(self, request, *args, **kwargs): self.object = self.get_object() data = {} if isinstance(self.object, XForm): raise ParseError(_(u"Data id not provided.")) elif(isinstance(self.object, Instance)): self.headers = merge_dicts( self.headers, last_modified_header(get_date(self.object, 'modified'))) if request.user.has_perm("change_xform", self.object.xform): return_url = request.QUERY_PARAMS.get('return_url') if not return_url: raise ParseError(_(u"return_url not provided.")) try: data["url"] = get_enketo_edit_url( request, self.object, return_url) except EnketoError as e: data['detail'] = "{}".format(e) else: raise PermissionDenied(_(u"You do not have edit permissions.")) return Response(data=data)
def enketo(self, request, *args, **kwargs): self.object = self.get_object() data = {} if isinstance(self.object, XForm): raise ParseError(_(u"Data id not provided.")) elif (isinstance(self.object, Instance)): self.headers = merge_dicts( self.headers, last_modified_header(get_date(self.object, 'modified'))) if request.user.has_perm("change_xform", self.object.xform): return_url = request.QUERY_PARAMS.get('return_url') if not return_url: raise ParseError(_(u"return_url not provided.")) try: data["url"] = get_enketo_edit_url(request, self.object, return_url) except EnketoError as e: data['detail'] = "{}".format(e) else: raise PermissionDenied(_(u"You do not have edit permissions.")) return Response(data=data)
def members(self, request, *args, **kwargs): organization = self.get_object() self.headers = merge_dicts( self.headers, last_modified_header(get_date(organization, 'joined'))) status_code = status.HTTP_200_OK data = [] username = request.DATA.get('username') or request.QUERY_PARAMS.get( 'username') if request.method in ['DELETE', 'POST', 'PUT'] and not username: status_code = status.HTTP_400_BAD_REQUEST data = {'username': [_(u"This field is required.")]} elif request.method == 'POST': data, status_code = _add_username_to_organization( organization, username) elif request.method == 'PUT': role = request.DATA.get('role') role_cls = ROLES.get(role) if not role or not role_cls: status_code = status.HTTP_400_BAD_REQUEST message = (_(u"'%s' is not a valid role." % role) if role else _(u"This field is required.")) data = {'role': [message]} else: _update_username_role(organization, username, role_cls) elif request.method == 'DELETE': data, status_code = _remove_username_to_organization( organization, username) if status_code in [status.HTTP_200_OK, status.HTTP_201_CREATED]: members = get_organization_members(organization) data = [u.username for u in members] return Response(data, status=status_code)
def members(self, request, *args, **kwargs): organization = self.get_object() self.headers = merge_dicts( self.headers, last_modified_header(get_date(organization, 'joined'))) status_code = status.HTTP_200_OK data = [] username = request.DATA.get('username') or request.QUERY_PARAMS.get( 'username') if request.method in ['DELETE', 'POST', 'PUT'] and not username: status_code = status.HTTP_400_BAD_REQUEST data = {'username': [_(u"This field is required.")]} elif request.method == 'POST': data, status_code = _add_username_to_organization( organization, username) elif request.method == 'PUT': role = request.DATA.get('role') role_cls = ROLES.get(role) if not role or not role_cls: status_code = status.HTTP_400_BAD_REQUEST message = (_(u"'%s' is not a valid role." % role) if role else _(u"This field is required.")) data = {'role': [message]} else: _update_username_role(organization, username, role_cls) elif request.method == 'DELETE': data, status_code = _remove_username_to_organization( organization, username) if status_code in [status.HTTP_200_OK, status.HTTP_201_CREATED]: members = get_organization_members(organization) data = [u.username for u in members] return Response(data, status=status_code)
class ConnectViewSet(ObjectLookupMixin, viewsets.GenericViewSet): """This endpoint allows you retrieve the authenticated user's profile info. # Retrieve profile > Example > > curl -X GET https://ona.io/api/v1/user > Response: > { "api_token": "76121138a080c5ae94f318a8b9be91e7ebebb484", "temp_token": "0668993ad2f9fa6a0bff58389996cf85f11894ca" "city": "Nairobi", "country": "Kenya", "gravatar": "avatar.png", "name": "Demo User", "email": "*****@*****.**", "organization": "", "require_auth": false, "twitter": "", "url": "http://localhost:8000/api/v1/profiles/demo", "user": "******", "username": "******", "website": "", } # Get projects that the authenticating user has starred <pre class="prettyprint"> <b>GET</b> /api/v1/user/<code>{username}</code>/starred</pre> # Request password reset <pre class="prettyprint"> <b>POST</b> /api/v1/user/reset </pre> - Sends an email to the user's email with a url that \ redirects to a reset password form on the API consumer's website. - `email` and `reset_url` are expected in the POST payload. - Expected reset_url format is `reset_url=https:/domain/path/to/reset/form`. - Example of reset url sent to user's email is\ `http://mydomain.com/reset_form?uid=Mg&token=2f3f334g3r3434&username=dXNlcg==`. - `uid` is the users `unique key` which is a base64 encoded integer value that\ can be used to access the users info at `/api/v1/users/<pk>` or \ `/api/v1/profiles/<pk>`. You can retrieve the integer value in `javascript` \ using the `window.atob();` function. `username` is a base64 encoded value of the user's username - `token` is a onetime use token that allows password reset > > Example > > curl -X POST -d [email protected]\ url=http://example-url.com/reset https://ona.io/api/v1/user/reset > > Response: > > HTTP 204 OK > # Reset user password <pre class="prettyprint"> <b>POST</b> /api/v1/user/reset </pre> - Resets user's password - `uid`, `token` and `new_password` are expected in the POST payload. - minimum password length is 4 characters > > Example > > curl -X POST -d uid=Mg -d token=qndoi209jf02n4 \ -d new_password=usernewpass https://ona.io/api/v1/user/reset > > Response: > > HTTP 204 OK > # Expire temporary token <pre class="prettyprint"> <b>DELETE</b> /api/v1/user/expire </pre> - Expires the temporary token > > Example > > curl -X DELETE https://ona.io/api/v1/user/expire \ -u <username>:<password> > > Response: > > HTTP 204 OK > """ lookup_field = 'user' queryset = UserProfile.objects.all() default_response_headers = last_modified_header( get_date(UserProfile.objects.last(), 'joined')) permission_classes = (ConnectViewsetPermissions, ) serializer_class = UserProfileWithTokenSerializer def list(self, request, *args, **kwargs): """ Returns authenticated user profile""" if request and not request.user.is_anonymous(): session = getattr(request, "session") if not session.session_key: # login user to create session token # TODO cannot call this without calling authenticate first or # setting the backend, commented for now. # login(request, request.user) session.set_expiry(DEFAULT_SESSION_EXPIRY_TIME) serializer = UserProfileWithTokenSerializer( instance=request.user.profile, context={"request": request}) return Response(serializer.data) @action(methods=['GET']) def starred(self, request, *args, **kwargs): """Return projects starred for this user.""" user_profile = self.get_object() self.headers = merge_dicts( self.headers, last_modified_header(get_date(user_profile, 'joined'))) user = user_profile.user projects = user.project_set.all() serializer = ProjectSerializer(projects, context={'request': request}, many=True) return Response(data=serializer.data) @list_route(methods=['POST']) def reset(self, request, *args, **kwargs): context = {'request': request} data = request.DATA if request.DATA is not None else {} if 'token' in request.DATA: serializer = PasswordResetChangeSerializer(data=data, context=context) else: serializer = PasswordResetSerializer(data=data, context=context) if serializer.is_valid(): serializer.save() return Response(status=status.HTTP_204_NO_CONTENT) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @list_route(methods=['DELETE']) def expire(self, request, *args, **kwargs): try: TempToken.objects.get(user=request.user).delete() except TempToken.DoesNotExist: raise ParseError(_(u"Temporary token not found!")) return Response(status=status.HTTP_204_NO_CONTENT)
class TeamViewSet(ModelViewSet): """ This endpoint allows you to create, update and view team information. ## GET List of Teams Provides a json list of teams and the projects the team is assigned to. <pre class="prettyprint"> <b>GET</b> /api/v1/teams </pre> > Example > > curl -X GET https://ona.io/api/v1/teams > Response > > [ > { > "url": "https://ona.io/api/v1/teams/1", > "name": "Owners", > "organization": "bruize", > "projects": [] > }, > { > "url": "https://ona.io/api/v1/teams/2", > "name": "demo team", > "organization": "bruize", > "projects": [] > } > ] ## GET Team Info for a specific team. Shows teams details and the projects the team is assigned to, where: * `pk` - unique identifier for the team <pre class="prettyprint"> <b>GET</b> /api/v1/teams/<code>{pk}</code> </pre> > Example > > curl -X GET https://ona.io/api/v1/teams/1 > Response > > { > "url": "https://ona.io/api/v1/teams/1", > "name": "Owners", > "organization": "bruize", > "projects": [] > } ## List members of a team A list of usernames is the response for members of the team. <pre class="prettyprint"> <b>GET</b> /api/v1/teams/<code>{pk}/members</code> </pre> > Example > > curl -X GET https://ona.io/api/v1/teams/1/members > Response > > ["member1"] > ## Add a user to a team POST `{"username": "******"}` to `/api/v1/teams/<pk>/members` to add a user to the specified team. A list of usernames is the response for members of the team. <pre class="prettyprint"> <b>POST</b> /api/v1/teams/<code>{pk}</code>/members </pre> > Response > > ["someusername"] """ queryset = Team.objects.all() default_response_headers = last_modified_header( get_date(Team.objects.last(), 'modified')) serializer_class = TeamSerializer lookup_field = 'pk' extra_lookup_fields = None permission_classes = [DjangoObjectPermissions] filter_backends = (filters.DjangoObjectPermissionsFilter,) @action(methods=['DELETE', 'GET', 'POST']) def members(self, request, *args, **kwargs): team = self.get_object() data = {} status_code = status.HTTP_200_OK if request.method in ['DELETE', 'POST']: username = request.DATA.get('username') or\ request.QUERY_PARAMS.get('username') if username: try: user = User.objects.get(username__iexact=username) except User.DoesNotExist: status_code = status.HTTP_400_BAD_REQUEST data['username'] = [ _(u"User `%(username)s` does not exist." % {'username': username})] else: if request.method == 'POST': add_user_to_team(team, user) elif request.method == 'DELETE': remove_user_from_team(team, user) status_code = status.HTTP_201_CREATED else: status_code = status.HTTP_400_BAD_REQUEST data['username'] = [_(u"This field is required.")] if status_code in [status.HTTP_200_OK, status.HTTP_201_CREATED]: data = [u.username for u in team.user_set.all()] return Response(data, status=status_code)
def retrieve(self, request, *args, **kwargs): xform = self.get_object() serializer = self.get_serializer(xform) dd = xform.data_dictionary() field_name = request.QUERY_PARAMS.get('field_name') fields = request.QUERY_PARAMS.get('fields') fmt = kwargs.get('format') self.headers = merge_dicts( self.headers, last_modified_header(get_date(xform, 'modified'))) if fields: if fmt is not None and fmt != 'json': raise ParseError("Error: only JSON format supported.") xform = self.get_object() self.headers['Last-Modified'] = get_date(xform, 'modified') context = self.get_serializer_context() serializer = FieldsChartSerializer(instance=xform, context=context) return Response(serializer.data) if field_name: # check if its the special _submission_time META if field_name == common_tags.SUBMISSION_TIME: field = common_tags.SUBMISSION_TIME else: # use specified field to get summary fields = filter( lambda f: f.name == field_name, [e for e in dd.survey_elements]) if len(fields) == 0: raise Http404( "Field %s does not not exist on the form" % field_name) field = fields[0] data = build_chart_data_for_field(xform, field) if request.accepted_renderer.format == 'json': xform = xform.pk elif request.accepted_renderer.format == 'html' and 'data' in data: for item in data['data']: if isinstance(item[field_name], list): item[field_name] = u', '.join(item[field_name]) data.update({ 'xform': xform }) return Response(data, template_name='chart_detail.html') if fmt != 'json' and field_name is None: raise ParseError("Not supported") data = serializer.data data["fields"] = {} for field in dd.survey_elements: field_url = get_form_field_chart_url(data["url"], field.name) data["fields"][field.name] = field_url return Response(data)
class ChartsViewSet(AnonymousUserPublicFormsMixin, viewsets.ReadOnlyModelViewSet): """ View chart for specific fields in a form or dataset. ### List of chart chart endpoints accessible to registered user <pre class="prettyprint"> <b>GET</b> /api/v1/charts</pre> > Example > > curl -X GET https://ona.io/api/v1/charts > Response > > [{ > > "id": 4240, > "id_string": "dhis2form" > "url": "https://ona.io/api/v1/charts/4240", > } > ... ### Get a list of chart field endpoints for a specific form or dataset. <pre class="prettyprint"> <b>GET</b> /api/v1/charts/<code>{formid}</code></pre> > Example > > curl -X GET https://ona.io/api/v1/charts/4240 > Response > > { > > "id": 4240, > "id_string": "dhis2form" > "url": "https://ona.io/api/v1/charts/4240", > "fields": { > "uuid": "https://ona.io/api/v1/charts/4240?field_name=uuid", > "num": "https://ona.io/api/v1/charts/4240?field_name=num", > ... > } > } ### Get a chart for a specific field in a form - `field_name` - a field name in the form - `format` - can be `html` or `json` <pre class="prettyprint"> <b>GET</b> /api/v1/charts/<code>{formid}</code>.<code>{format}</code>?\ field_name=<code>field_name</code></pre> > Example > > curl -X GET https://ona.io/api/v1/charts/4240.html?field_name=age > Response > > - `html` format response is a html, javascript and css to the chart > - `json` format response is the `JSON` data that can be passed to a charting > library ### Get a chart data for all fields in a form The only field ommitted is instanceID since it is unique for every record. - `fields` - is a comma separated list of fields to be included in the \ response. If `fields=all` then all the fields of the form will be returned. <pre class="prettyprint"> <b>GET</b> /api/v1/charts/<code>{formid}</code>?<code>fields=all</code> </pre> > Example > > curl -X GET https://ona.io/api/v1/charts/4240?fields=all > Response > > - `json` format response is the `JSON` data for each field that can be > passed to a charting library """ filter_backends = (filters.AnonDjangoObjectPermissionFilter, ) queryset = XForm.objects.all() default_response_headers = last_modified_header( get_date(XForm.objects.last(), 'modified')) serializer_class = ChartSerializer lookup_field = 'pk' renderer_classes = ( ChartBrowsableAPIRenderer, JSONRenderer, TemplateHTMLRenderer, ) permission_classes = [ XFormPermissions, ] def retrieve(self, request, *args, **kwargs): xform = self.get_object() serializer = self.get_serializer(xform) dd = xform.data_dictionary() field_name = request.QUERY_PARAMS.get('field_name') fields = request.QUERY_PARAMS.get('fields') fmt = kwargs.get('format') self.headers = merge_dicts( self.headers, last_modified_header(get_date(xform, 'modified'))) if fields: if fmt is not None and fmt != 'json': raise ParseError("Error: only JSON format supported.") xform = self.get_object() self.headers['Last-Modified'] = get_date(xform, 'modified') context = self.get_serializer_context() serializer = FieldsChartSerializer(instance=xform, context=context) return Response(serializer.data) if field_name: # check if its the special _submission_time META if field_name == common_tags.SUBMISSION_TIME: field = common_tags.SUBMISSION_TIME else: # use specified field to get summary fields = filter(lambda f: f.name == field_name, [e for e in dd.survey_elements]) if len(fields) == 0: raise Http404("Field %s does not not exist on the form" % field_name) field = fields[0] data = build_chart_data_for_field(xform, field) if request.accepted_renderer.format == 'json': xform = xform.pk elif request.accepted_renderer.format == 'html' and 'data' in data: for item in data['data']: if isinstance(item[field_name], list): item[field_name] = u', '.join(item[field_name]) data.update({'xform': xform}) return Response(data, template_name='chart_detail.html') if fmt != 'json' and field_name is None: raise ParseError("Not supported") data = serializer.data data["fields"] = {} for field in dd.survey_elements: field_url = get_form_field_chart_url(data["url"], field.name) data["fields"][field.name] = field_url return Response(data)
class OrganizationProfileViewSet(ObjectLookupMixin, ModelViewSet): """ List, Retrieve, Update, Create/Register Organizations ## Register a new Organization <pre class="prettyprint"><b>POST</b> /api/v1/orgs</pre> > Example > > { > "org": "modilabs", > "name": "Modi Labs Research", > "email": "*****@*****.**", > "city": "New York", > "country": "US", > ... > } ## List of Organizations <pre class="prettyprint"><b>GET</b> /api/v1/orgs</pre> > Example > > curl -X GET https://ona.io/api/v1/orgs > Response > > [ > { > "url": "https://ona.io/api/v1/orgs/modilabs", > "org": "modilabs", > "name": "Modi Labs Research", > "email": "*****@*****.**", > "city": "New York", > "country": "US", > "website": "", > "twitter": "", > "gravatar": "https://secure.gravatar.com/avatar/xxxxxx", > "require_auth": false, > "user": "******" > "creator": "https://ona.io/api/v1/users/demo" > }, > { > ...}, ... > ] ## Retrieve Organization Profile Information <pre class="prettyprint"><b>GET</b> /api/v1/orgs/{username}</pre> > Example > > curl -X GET https://ona.io/api/v1/orgs/modilabs > Response > > { > "url": "https://ona.io/api/v1/orgs/modilabs", > "org": "modilabs", > "name": "Modi Labs Research", > "email": "*****@*****.**", > "city": "New York", > "country": "US", > "website": "", > "twitter": "", > "gravatar": "https://secure.gravatar.com/avatar/xxxxxx", > "require_auth": false, > "user": "******" > "creator": "https://ona.io/api/v1/users/demo" > } ## List Organization members Get a list of organization members. <pre class="prettyprint"><b>GET</b> /api/v1/orgs/{username}/members</pre> > Example > > curl -X GET https://ona.io/api/v1/orgs/modilabs/members > Response > > ["member1", "member2"] ## Add a user to an organization To add a user to an organization requires a JSON payload of `{"username": "******"}`. <pre class="prettyprint"><b>POST</b> /api/v1/orgs/{username}/members</pre> > Example > > curl -X POST -d '{"username": "******"}' \ https://ona.io/api/v1/orgs/modilabs/members -H "Content-Type: application/json" > Response > > ["member1"] ## Change the role of a user in an organization To change the role of a user in an organization pass the username and role `{"username": "******", "role": "owner|manager|editor|dataentry|readonly"}`. <pre class="prettyprint"><b>PUT</b> /api/v1/orgs/{username}/members</pre> > Example > > curl -X PUT -d '{"username": "******", "role": "editor"}' \ https://ona.io/api/v1/orgs/modilabs/members -H "Content-Type: application/json" > Response > > ["member1"] ## Remove a user from an organization To remove a user from an organization requires a JSON payload of `{"username": "******"}`. <pre class="prettyprint"><b>DELETE</b> /api/v1/orgs/{username}/members</pre> > Example > > curl -X DELETE -d '{"username": "******"}' \ https://ona.io/api/v1/orgs/modilabs/members -H "Content-Type: application/json" > Response > > [] """ queryset = OrganizationProfile.objects.all() default_response_headers = last_modified_header( get_date(OrganizationProfile.objects.last(), 'joined')) serializer_class = OrganizationSerializer lookup_field = 'user' permission_classes = [permissions.DjangoObjectPermissions] filter_backends = (OrganizationPermissionFilter, ) @action(methods=['DELETE', 'GET', 'POST', 'PUT']) def members(self, request, *args, **kwargs): organization = self.get_object() self.headers = merge_dicts( self.headers, last_modified_header(get_date(organization, 'joined'))) status_code = status.HTTP_200_OK data = [] username = request.DATA.get('username') or request.QUERY_PARAMS.get( 'username') if request.method in ['DELETE', 'POST', 'PUT'] and not username: status_code = status.HTTP_400_BAD_REQUEST data = {'username': [_(u"This field is required.")]} elif request.method == 'POST': data, status_code = _add_username_to_organization( organization, username) elif request.method == 'PUT': role = request.DATA.get('role') role_cls = ROLES.get(role) if not role or not role_cls: status_code = status.HTTP_400_BAD_REQUEST message = (_(u"'%s' is not a valid role." % role) if role else _(u"This field is required.")) data = {'role': [message]} else: _update_username_role(organization, username, role_cls) elif request.method == 'DELETE': data, status_code = _remove_username_to_organization( organization, username) if status_code in [status.HTTP_200_OK, status.HTTP_201_CREATED]: members = get_organization_members(organization) data = [u.username for u in members] return Response(data, status=status_code)
class UserViewSet(ReadOnlyModelViewSet): """ This endpoint allows you to list and retrieve user's first and last names. ## List Users > Example > > curl -X GET https://ona.io/api/v1/users > Response: > [ > { > "username": "******", > "first_name": "First", > "last_name": "Last" > }, > { > "username": "******", > "first_name": "Another", > "last_name": "Demo" > }, > ... > ] ## Retrieve a specific user info <pre class="prettyprint"><b>GET</b> /api/v1/users/{username}</pre> > Example: > > curl -X GET https://ona.io/api/v1/users/demo > Response: > > { > "username": "******", > "first_name": "First", > "last_name": "Last" > } ## Search for a users using email > Example > > curl -X GET https://ona.io/api/v1/[email protected] > Response: > [ > { > "username": "******", > "first_name": "First", > "last_name": "Last" > }, > { > "username": "******", > "first_name": "Another", > "last_name": "Demo" > }, > ... > ] """ queryset = User.objects.exclude(pk=settings.ANONYMOUS_USER_ID) default_response_headers = last_modified_header( get_date(User.objects.last(), 'joined')) serializer_class = UserSerializer lookup_field = 'username' permission_classes = [permissions.UserViewSetPermissions] filter_backends = (filters.SearchFilter, ) search_fields = ('=email', ) def get_object(self, queryset=None): """Lookup a username by pk else use lookup_field""" if queryset is None: queryset = self.filter_queryset(self.get_queryset()) lookup = self.kwargs.get(self.lookup_field) filter_kwargs = {self.lookup_field: lookup} try: pk = int(lookup) except ValueError: pass else: filter_kwargs = {'pk': pk} obj = get_object_or_404(queryset, **filter_kwargs) # May raise a permission denied self.check_object_permissions(self.request, obj) return obj
class StatsViewSet(AnonymousUserPublicFormsMixin, viewsets.ReadOnlyModelViewSet): """ Stats summary for median, mean, mode, range, max, min. A query parameter `method` can be used to limit the results to either `mean`, `median`, `mode` or `range` only results. Example: GET /api/v1/stats/1? Response: [ { "age": { "median": 8, "mean": 23.4, "mode": 23, "range": 24, "max": 28, "min": 4 }, ... ] Example: GET /api/v1/stats/1?method=median Response: [ { "age": { "median": 8, }, ... ] """ lookup_field = 'pk' queryset = XForm.objects.all() default_response_headers = last_modified_header( get_date(XForm.objects.last(), 'modified')) filter_backends = (filters.AnonDjangoObjectPermissionFilter, ) permission_classes = [ XFormPermissions, ] serializer_class = StatsSerializer def get_serializer_class(self): lookup = self.kwargs.get(self.lookup_field) if lookup is not None: serializer_class = StatsInstanceSerializer else: serializer_class = \ super(StatsViewSet, self).get_serializer_class() return serializer_class
class MetaDataViewSet(viewsets.ModelViewSet): """ This endpoint provides access to form metadata, for example, supporting documents, media files to be used in the form, source documents and map layers. - `pk` - primary key for the metadata - `formid` - the form id for a form - `format` - is the extension of a file format e.g `png`, `csv` ### Permissions This endpoint applys the same permissions someone has on the form. ## Get list of metadata Returns a list of metadata accross all forms requesting user has access to. <pre class="prettyprint">GET /api/v1/metadata</pre> HTTP 200 OK [ { "data_file": "", "data_file_type": null, "data_type": "public_link", "data_value": "http://mylink", "id": 406, "url": "https://ona.io/api/v1/metadata/406", "xform": 328 }, { "data_file": "username/form-media/a.png", "data_file_type": "image/png", "data_type": "media", "data_value": "a.png", "id": 7100, "url": "https://ona.io/api/v1/metadata/7100", "xform": 320 }, .... ] ## Get list of metadata for a specific form The form endpoint, `/api/v1/forms/formid`, contains a `metadata` field has list of metadata for the form. Alternatively, you can supply the query parameter `xform` with the `formid` as the value. <pre class="prettyprint"> GET /api/v1/metdata?<code>xform=formid</code></pre> HTTP 200 OK [ { "data_file": "username/form-media/a.png", "data_file_type": "image/png", "data_type": "media", "data_value": "a.png", "id": 7100, "url": "https://ona.io/api/v1/metadata/7100", "xform": 320 }, .... ] ## Get a specific metadata <pre class="prettyprint"> GET /api/v1/metadata/<code>{pk}</code></pre> curl -X GET https://ona.io/api/v1/metadata/7100 HTTP 200 OK { "data_file": "username/form-media/a.png", "data_file_type": "image/png", "data_type": "media", "data_value": "a.png", "id": 7100, "url": "https://ona.io/api/v1/metadata/7100", "xform": 320 } If the metadata is a file, appending the extension of the file type would return the file itself e.g: <pre class="prettyprint"> GET /api/v1/metadata/<code>{pk}.{format}</code></pre> curl -X GET https://ona.io/api/v1/metadata/7100.png -o a.png Alternatively, if the request is made with an `Accept` header of the content type of the file the file would be returned e.g <pre class="prettyprint">GET /api/v1/metadata/<code>{pk}</code> \ Accept: image/png </pre> curl -X GET https://ona.io/api/v1/metadata/7100 \ -H "Accept: image/png" -o a.png ## Add metadata or media file to a form <pre class="prettyprint">POST /api/v1/metadata</pre> Payload {"xform": <formid>, "data_type": "<data_type>", \ "data_value": "<data_value>"} Where: - `data_type` - can be 'media' or 'source' or 'supporting_doc' - `data_value` - can be text or a file name - `xform` - the form id you are adding the media to - `data_file` - optional, should be the file you want to upload Example: curl -X POST -d "{"data_type": "mapbox_layer", "data_value": \ "example||https://api.tiles.mapbox.com/v3/examples.map-0l53fhk2.json||example\ attribution", "xform": 320}" https://ona.io/api/v1/metadata \ -H "Content-Type: appliction/json" HTTP 201 CREATED { "id": 7119, "xform": 320, "data_value": "example||https://api.tiles.mapbox.com/v3/examples\ .map-0l53fhk2.json||example attribution", "data_type": "mapbox_layer", "data_file": null, "data_file_type": null, "url": "https://ona.io/api/v1/metadata/7119.json" } Media upload example: curl -X POST -F 'data_type=media' -F 'data_value=demo.jpg' \ -F 'xform=320' -F "[email protected]" https://ona.io/api/v1/metadata.json HTTP 201 CREATED { "id": 7121, "xform": 320, "data_value": "folder.jpg", "data_type": "media", "data_file": "ukanga/formid-media/folder.jpg", "data_file_type": "image/jpeg", "url": "https://ona.io/api/v1/metadata/7121.json" } ## Delete Metadata <pre class="prettyprint">DELETE /api/v1/metadata/<code>{pk}</code></pre> """ content_negotiation_class = MediaFileContentNegotiation filter_backends = (filters.MetaDataFilter, ) queryset = MetaData.objects.all() default_response_headers = last_modified_header( get_date(MetaData.objects.last(), 'modified')) permission_classes = (MetaDataObjectPermissions, ) renderer_classes = (renderers.JSONRenderer, renderers.BrowsableAPIRenderer, MediaFileRenderer) serializer_class = MetaDataSerializer def retrieve(self, request, *args, **kwargs): self.object = self.get_object() if isinstance(request.accepted_renderer, MediaFileRenderer) \ and self.object.data_file is not None: return get_media_file_response(self.object) serializer = self.get_serializer(self.object) return Response(serializer.data)
class AttachmentViewSet(viewsets.ReadOnlyModelViewSet): """ ## Lists attachments of all xforms > GET /api/v1/media/ > Example > > curl -X GET https://ona.io/api/v1/media > Response > > [{ > "download_url": "http://ona.io/api/v1/media/1.jpg", > "small_download_url": "http://ona.io/api/v1/media/1-small.jpg", > "medium_download_url": "http://ona.io/api/v1/media/\ 1-medium.jpg", > "filename": "doe/attachments/1408520136827.jpg", > "id": 1, > "instance": 1, > "mimetype": "image/jpeg", > "url": "http://ona.io/api/v1/media/1", > "xform": 1, > } > ... ## Retrieve details of an attachment ><pre class="prettyprint"> GET /api/v1/media/<code>{pk}</code></pre> > > Example > > curl -X GET https://ona.io/api/v1/media/1 > Response > > { > "download_url": "http://ona.io/api/v1/media/1.jpg", > "small_download_url": "http://ona.io/api/v1/media/1-small.jpg", > "medium_download_url": "http://ona.io/api/v1/media/\ 1-medium.jpg", > "filename": "doe/attachments/1408520136827.jpg", > "id": 1, > "instance": 1, > "mimetype": "image/jpeg", > "url": "http://ona.io/api/v1/media/1", > "xform": 1, > } ## Retrieve an attachment file ><pre class="prettyprint"> > GET /api/v1/media/<code>{pk}.{format}</code></pre> > > curl -X GET https://ona.io/api/v1/media/1.png -o a.png Alternatively, if the request is made with an `Accept` header of the content type of the file the file would be returned e.g ><pre class="prettyprint"> > GET /api/v1/media/<code>{pk}</code> Accept: image/png </pre> > > Example > > curl -X GET https://ona.io/api/v1/media/1 -H "Accept: image/png" -o > a.png ## Lists attachments of a specific xform ><pre class="prettyprint"> > GET /api/v1/media/?xform=<code>{xform}</code></pre> > > Example > > curl -X GET https://ona.io/api/v1/media?xform=1 > Response > > [{ > "download_url": "http://ona.io/api/v1/media/1.jpg", > "small_download_url": "http://ona.io/api/v1/media/1-small.jpg", > "medium_download_url": "http://ona.io/api/v1/media/\ 1-medium.jpg", > "filename": "doe/attachments/1408520136827.jpg", > "id": 1, > "instance": 1, > "mimetype": "image/jpeg", > "url": "http://ona.io/api/v1/media/1", > "xform": 1, > } > ... ## Lists attachments of a specific instance ><pre class="prettyprint"> > GET /api/v1/media?instance=<code>{instance}</code></pre> > > Example > > curl -X GET https://ona.io/api/v1/media?instance=1 > Response > > [{ > "download_url": "http://ona.io/api/v1/media/1.jpg", > "small_download_url": "http://ona.io/api/v1/media/1-small.jpg", > "medium_download_url": "http://ona.io/api/v1/media/\ 1-medium.jpg", > "filename": "doe/attachments/1408520136827.jpg", > "id": 1, > "instance": 1, > "mimetype": "image/jpeg", > "url": "http://ona.io/api/v1/media/1", > "xform": 1, > } > ... ## Retrieve image link of an attachment ><pre class="prettyprint"> GET /api/v1/media/<code>{pk}</code></pre> > > Example > > curl -X GET https://ona.io/api/v1/media/1\ ?filename=doe/attachments/1408520136827.jpg > Response > > http://ona.io/api/v1/media/1.jpg """ content_negotiation_class = MediaFileContentNegotiation filter_backends = (filters.AttachmentFilter, ) lookup_field = 'pk' queryset = Attachment.objects.all() default_response_headers = last_modified_header( get_date(Attachment.objects.last(), 'modified')) permission_classes = (AttachmentObjectPermissions, ) serializer_class = AttachmentSerializer renderer_classes = (renderers.JSONRenderer, renderers.BrowsableAPIRenderer, MediaFileRenderer) def retrieve(self, request, *args, **kwargs): self.object = self.get_object() self.headers = merge_dicts( self.headers, last_modified_header(get_date(self.object, 'modified'))) if isinstance(request.accepted_renderer, MediaFileRenderer) \ and self.object.media_file is not None: data = self.object.media_file.read() return Response(data, content_type=self.object.mimetype) filename = request.QUERY_PARAMS.get('filename') serializer = self.get_serializer(self.object) if filename: if filename == self.object.media_file.name: return Response(serializer.get_download_url(self.object)) else: raise Http404(_("Filename '%s' not found." % filename)) return Response(serializer.data)
class ProjectViewSet(LabelsMixin, ModelViewSet): """ List, Retrieve, Update, Create Project and Project Forms Where: - `pk` - is the project id - `formid` - is the form id - `owner` - is the username for the user or organization of the project ## Register a new Project <pre class="prettyprint"> <b>POST</b> /api/v1/projects</pre> > Example > > { > "url": "https://ona.io/api/v1/projects/1", > "owner": "https://ona.io/api/v1/users/ona", > "name": "project 1", > "date_created": "2013-07-24T13:37:39Z", > "date_modified": "2013-07-24T13:37:39Z" > } ## List of Projects <pre class="prettyprint"><b>GET</b> /api/v1/projects</pre> > Example > > curl -X GET https://ona.io/api/v1/projects > Response > > [ > { > "url": "https://ona.io/api/v1/projects/1", > "owner": "https://ona.io/api/v1/users/ona", > "name": "project 1", > "date_created": "2013-07-24T13:37:39Z", > "date_modified": "2013-07-24T13:37:39Z" > }, > { > "url": "https://ona.io/api/v1/projects/4", > "owner": "https://ona.io/api/v1/users/ona", > "name": "project 2", > "date_created": "2013-07-24T13:59:10Z", > "date_modified": "2013-07-24T13:59:10Z" > }, ... > ] ## List of Projects filter by owner/organization <pre class="prettyprint"> <b>GET</b> /api/v1/projects?<code>owner</code>=<code>owner_username</code> </pre> > Example > > curl -X GET https://ona.io/api/v1/projects?owner=ona ## Retrieve Project Information <pre class="prettyprint"> <b>GET</b> /api/v1/projects/<code>{pk}</code></pre> > Example > > curl -X GET https://ona.io/api/v1/projects/1 > Response > > { > "url": "https://ona.io/api/v1/projects/1", > "owner": "https://ona.io/api/v1/users/ona", > "name": "project 1", > "date_created": "2013-07-24T13:37:39Z", > "date_modified": "2013-07-24T13:37:39Z" > } ## Update Project Information <pre class="prettyprint"> <b>PUT</b> /api/v1/projects/<code>{pk}</code> or \ <b>PATCH</b> /api/v1/projects/<code>{pk}</code></pre></pre> > Example > curl -X PATCH -d 'metadata={"description": "Lorem ipsum",\ "location": "Nakuru, Kenya",\ "category": "water"}' \ https://ona.io/api/v1/projects/1 > Response > > { > "url": "https://ona.io/api/v1/projects/1", > "owner": "https://ona.io/api/v1/users/ona", > "name": "project 1", > "metadata": { > "description": "Lorem ipsum", > "location": "Nakuru, Kenya", > "category": "water" > } > "date_created": "2013-07-24T13:37:39Z", > "date_modified": "2013-07-24T13:37:39Z" > } ## Share a project with a specific user You can share a project with a specific user by `POST` a payload with - `username` of the user you want to share the form with and - `role` you want the user to have on the project. \ Available roles are `readonly`, `dataentry`, `editor`, `manager`. <pre class="prettyprint"> <b>POST</b> /api/v1/projects/<code>{pk}</code>/share </pre> > Example > > curl -X POST -d username=alice -d role=readonly\ https://ona.io/api/v1/projects/1/share > Response > > HTTP 204 NO CONTENT ## Send an email to users on project share An email is only sent when the `email_msg` request variable is present. <pre class="prettyprint"> <b>POST</b> /api/v1/projects/<code>{pk}</code>/share </pre> > Example > > curl -X POST -d username=alice -d role=readonly -d email_msg=I have\ shared the project with you\ https://ona.io/api/v1/projects/1/share > Response > > HTTP 204 NO CONTENT ## Remove a user from a project You can remove a specific user from a project using `POST` with payload: - `username` of the user you want to remove - `role` the user has on the project - `remove` set remove to True > Example > > curl -X POST -d "username=alice" -d "role=readonly" \ -d "remove=True" http://localhost:8000/api/v1/projects/1/share > Response > > HTTP 204 NO CONTENT ## Assign a form to a project To [re]assign an existing form to a project you need to `POST` a payload of `formid=FORMID` to the endpoint below. <pre class="prettyprint"> <b>POST</b> /api/v1/projects/<code>{pk}</code>/forms</pre> > Example > > curl -X POST -d '{"formid": 28058}' \ https://ona.io/api/v1/projects/1/forms -H "Content-Type: application/json" > Response > > { > "url": "https://ona.io/api/v1/forms/28058", > "formid": 28058, > "uuid": "853196d7d0a74bca9ecfadbf7e2f5c1f", > "id_string": "Birds", > "sms_id_string": "Birds", > "title": "Birds", > "allows_sms": false, > "bamboo_dataset": "", > "description": "", > "downloadable": true, > "encrypted": false, > "owner": "ona", > "public": false, > "public_data": false, > "date_created": "2013-07-25T14:14:22.892Z", > "date_modified": "2013-07-25T14:14:22.892Z" > } ## Upload XLSForm to a project <pre class="prettyprint"> <b>POST</b> /api/v1/projects/<code>{pk}</code>/forms</pre> > Example > > curl -X POST -F xls_file=@/path/to/form.xls\ https://ona.io/api/v1/projects/1/forms > Response > > { > "url": "https://ona.io/api/v1/forms/28058", > "formid": 28058, > "uuid": "853196d7d0a74bca9ecfadbf7e2f5c1f", > "id_string": "Birds", > "sms_id_string": "Birds", > "title": "Birds", > "allows_sms": false, > "bamboo_dataset": "", > "description": "", > "downloadable": true, > "encrypted": false, > "owner": "ona", > "public": false, > "public_data": false, > "date_created": "2013-07-25T14:14:22.892Z", > "date_modified": "2013-07-25T14:14:22.892Z" > } ## Get forms for a project <pre class="prettyprint"> <b>GET</b> /api/v1/projects/<code>{pk}</code>/forms </pre> > Example > > curl -X GET https://ona.io/api/v1/projects/1/forms > Response > > [ > { > "url": "https://ona.io/api/v1/forms/28058", > "formid": 28058, > "uuid": "853196d7d0a74bca9ecfadbf7e2f5c1f", > "id_string": "Birds", > "sms_id_string": "Birds", > "title": "Birds", > "allows_sms": false, > "bamboo_dataset": "", > "description": "", > "downloadable": true, > "encrypted": false, > "owner": "ona", > "public": false, > "public_data": false, > "date_created": "2013-07-25T14:14:22.892Z", > "date_modified": "2013-07-25T14:14:22.892Z", > "tags": [], > "users": [ > { > "role": "owner", > "user": "******", > "permissions": ["report_xform", ...] > }, > ... > ] > }, > ... > ] ## Get list of projects with specific tag(s) Use the `tags` query parameter to filter the list of projects, `tags` should be a comma separated list of tags. <pre class="prettyprint"> <b>GET</b> /api/v1/projects?<code>tags</code>=<code>tag1,tag2</code></pre> List projects tagged `smart` or `brand new` or both. > Request > > curl -X GET https://ona.io/api/v1/projects?tag=smart,brand+new > Response > HTTP 200 OK > > [ > { > "url": "https://ona.io/api/v1/projects/1", > "owner": "https://ona.io/api/v1/users/ona", > "name": "project 1", > "date_created": "2013-07-24T13:37:39Z", > "date_modified": "2013-07-24T13:37:39Z" > }, > ... > ] ## Get list of Tags for a specific Project <pre class="prettyprint"> <b>GET</b> /api/v1/project/<code>{pk}</code>/labels </pre> > Request > > curl -X GET https://ona.io/api/v1/projects/28058/labels > Response > > ["old", "smart", "clean house"] ## Tag a Project A `POST` payload of parameter `tags` with a comma separated list of tags. Examples - `animal fruit denim` - space delimited, no commas - `animal, fruit denim` - comma delimited <pre class="prettyprint"> <b>POST</b> /api/v1/projects/<code>{pk}</code>/labels </pre> Payload {"tags": "tag1, tag2"} ## Remove a tag from a Project <pre class="prettyprint"> <b>DELETE</b> /api/v1/projects/<code>{pk}</code>/labels/<code>tag_name</code> </pre> > Request > > curl -X DELETE \ https://ona.io/api/v1/projects/28058/labels/tag1 > > or to delete the tag "hello world" > > curl -X DELETE \ https://ona.io/api/v1/projects/28058/labels/hello%20world > > Response > > HTTP 200 OK ## Add a star to a project <pre class="prettypriProjectnt"> <b>POST</b> /api/v1/projects/<code>{pk}</code>/star</pre> ## Remove a star to a project <pre class="prettyprint"> <b>DELETE</b> /api/v1/projects/<code>{pk}</code>/star</pre> ## Get user profiles that have starred a project <pre class="prettyprint"> <b>GET</b> /api/v1/projects/<code>{pk}</code>/star</pre> """ queryset = Project.objects.all() default_response_headers = last_modified_header( get_date(Project.objects.last(), 'modified')) serializer_class = ProjectSerializer lookup_field = 'pk' extra_lookup_fields = None permission_classes = [ProjectPermissions] filter_backends = (AnonUserProjectFilter, ProjectOwnerFilter, TagFilter) @action(methods=['POST', 'GET']) def forms(self, request, **kwargs): """Add a form to a project or list forms for the project. The request key `xls_file` holds the XLSForm file object. """ project = get_object_or_404(Project, pk=kwargs.get('pk')) self.headers = merge_dicts( self.headers, last_modified_header(get_date(project, 'modified'))) if request.method.upper() == 'POST': survey = utils.publish_project_xform(request, project) if isinstance(survey, XForm): xform = XForm.objects.get(pk=survey.pk) serializer = XFormSerializer(xform, context={'request': request}) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(survey, status=status.HTTP_400_BAD_REQUEST) project_xforms = project.projectxform_set.values('xform') xforms = XForm.objects.filter(pk__in=project_xforms) serializer = XFormSerializer(xforms, context={'request': request}, many=True) return Response(serializer.data) @action(methods=['PUT']) def share(self, request, *args, **kwargs): self.object = self.get_object() data = dict(request.DATA.items() + [('project', self.object.pk)]) serializer = ShareProjectSerializer(data=data) if serializer.is_valid(): if data.get("remove"): serializer.remove_user() else: serializer.save() email_msg = data.get('email_msg') if email_msg: # send out email message. user = serializer.object.user send_mail(SHARE_PROJECT_SUBJECT.format(self.object.name), email_msg, DEFAULT_FROM_EMAIL, (user.email, )) else: return Response(data=serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(status=status.HTTP_204_NO_CONTENT) @action(methods=['DELETE', 'GET', 'POST']) def star(self, request, *args, **kwargs): user = request.user project = get_object_or_404(Project, pk=kwargs.get('pk')) self.headers = merge_dicts( self.headers, last_modified_header(get_date(project, 'modified'))) if request.method == 'DELETE': project.user_stars.remove(user) elif request.method == 'POST': project.user_stars.add(user) elif request.method == 'GET': users = project.user_stars.values('pk') user_profiles = UserProfile.objects.filter(user__in=users) serializer = UserProfileSerializer(user_profiles, context={'request': request}, many=True) return Response(serializer.data) return Response(status=status.HTTP_204_NO_CONTENT)
class XFormViewSet(AnonymousUserPublicFormsMixin, LabelsMixin, ModelViewSet): """ Publish XLSForms, List, Retrieve Published Forms. Where: - `pk` - is the form unique identifier ## Upload XLSForm To publish and xlsform, you need to provide either the xlsform via `xls_file` \ parameter or a link to the xlsform via the `xls_url` parameter. Optionally, you can specify the target account where the xlsform should be \ published using the `owner` parameter, which specifies the username to the account. - `xls_file`: the xlsform file. - `xls_url`: the url to an xlsform - `owner`: username to the target account (Optional) <pre class="prettyprint"> <b>POST</b> /api/v1/forms</pre> > Example > > curl -X POST -F xls_file=@/path/to/form.xls \ https://ona.io/api/v1/forms > > OR post an xlsform url > > curl -X POST -d \ "xls_url=https://ona.io/ukanga/forms/tutorial/form.xls" \ https://ona.io/api/v1/forms > Response > > { > "url": "https://ona.io/api/v1/forms/28058", > "formid": 28058, > "uuid": "853196d7d0a74bca9ecfadbf7e2f5c1f", > "id_string": "Birds", > "sms_id_string": "Birds", > "title": "Birds", > "allows_sms": false, > "bamboo_dataset": "", > "description": "", > "downloadable": true, > "encrypted": false, > "owner": "ona", > "public": false, > "public_data": false, > "date_created": "2013-07-25T14:14:22.892Z", > "date_modified": "2013-07-25T14:14:22.892Z" > } ## Get list of forms <pre class="prettyprint"> <b>GET</b> /api/v1/forms</pre> > Request > > curl -X GET https://ona.io/api/v1/forms ## Get list of forms filter by owner <pre class="prettyprint"> <b>GET</b> /api/v1/forms?<code>owner</code>=<code>owner_username</code></pre> > Request > > curl -X GET https://ona.io/api/v1/forms?owner=ona ## Get Form Information <pre class="prettyprint"> <b>GET</b> /api/v1/forms/<code>{pk}</code></pre> > Example > > curl -X GET https://ona.io/api/v1/forms/28058 > Response > > { > "url": "https://ona.io/api/v1/forms/28058", > "formid": 28058, > "uuid": "853196d7d0a74bca9ecfadbf7e2f5c1f", > "id_string": "Birds", > "sms_id_string": "Birds", > "title": "Birds", > "allows_sms": false, > "bamboo_dataset": "", > "description": "", > "downloadable": true, > "encrypted": false, > "owner": "https://ona.io/api/v1/users/ona", > "public": false, > "public_data": false, > "require_auth": false, > "date_created": "2013-07-25T14:14:22.892Z", > "date_modified": "2013-07-25T14:14:22.892Z" > } ## Set Form Information You can use `PUT` or `PATCH` http methods to update or set form data elements. If you are using `PUT`, you have to provide the `uuid, description, downloadable, owner, public, public_data, title, xls_file` fields. With `PATCH` you only need provide at least one of the fields. - `xls_file`: Can only be updated when there are no submissions. <pre class="prettyprint"> <b>PATCH</b> /api/v1/forms/<code>{pk}</code></pre> > Example > > curl -X PATCH -d "public=True" -d "description=Le description"\ https://ona.io/api/v1/forms/28058 > Response > > { > "url": "https://ona.io/api/v1/forms/28058", > "formid": 28058, > "uuid": "853196d7d0a74bca9ecfadbf7e2f5c1f", > "id_string": "Birds", > "sms_id_string": "Birds", > "title": "Birds", > "allows_sms": false, > "bamboo_dataset": "", > "description": "Le description", > "downloadable": true, > "encrypted": false, > "owner": "https://ona.io/api/v1/users/ona", > "public": true, > "public_data": false, > "date_created": "2013-07-25T14:14:22.892Z", > "date_modified": "2013-07-25T14:14:22.892Z" > } ## Delete Form <pre class="prettyprint"> <b>DELETE</b> /api/v1/forms/<code>{pk}</code></pre> > Example > > curl -X DELETE https://ona.io/api/v1/forms/28058 > > Response > > HTTP 204 NO CONTENT ## List Forms <pre class="prettyprint"> <b>GET</b> /api/v1/forms </pre> > Example > > curl -X GET https://ona.io/api/v1/forms > Response > > [{ > "url": "https://ona.io/api/v1/forms/28058", > "formid": 28058, > "uuid": "853196d7d0a74bca9ecfadbf7e2f5c1f", > "id_string": "Birds", > "sms_id_string": "Birds", > "title": "Birds", > ... > }, ...] ## Get `JSON` | `XML` | `XLS` Form Representation <pre class="prettyprint"> <b>GET</b> /api/v1/forms/<code>{pk}</code>/form.\ <code>{format}</code></pre> > JSON Example > > curl -X GET https://ona.io/api/v1/forms/28058/form.json > Response > > { > "name": "Birds", > "title": "Birds", > "default_language": "default", > "id_string": "Birds", > "type": "survey", > "children": [ > { > "type": "text", > "name": "name", > "label": "1. What is your name?" > }, > ... > ] > } > XML Example > > curl -X GET https://ona.io/api/v1/forms/28058/form.xml > Response > > <?xml version="1.0" encoding="utf-8"?> > <h:html xmlns="http://www.w3.org/2002/xforms" ...> > <h:head> > <h:title>Birds</h:title> > <model> > <itext> > ..... > </h:body> > </h:html> > XLS Example > > curl -X GET https://ona.io/api/v1/forms/28058/form.xls > Response > > Xls file downloaded ## Get list of forms with specific tag(s) Use the `tags` query parameter to filter the list of forms, `tags` should be a comma separated list of tags. <pre class="prettyprint"> <b>GET</b> /api/v1/forms?<code>tags</code>=<code>tag1,tag2</code></pre> List forms tagged `smart` or `brand new` or both. > Request > > curl -X GET https://ona.io/api/v1/forms?tag=smart,brand+new > Response > HTTP 200 OK > > [{ > "url": "https://ona.io/api/v1/forms/28058", > "formid": 28058, > "uuid": "853196d7d0a74bca9ecfadbf7e2f5c1f", > "id_string": "Birds", > "sms_id_string": "Birds", > "title": "Birds", > ... > }, ...] ## Get list of Tags for a specific Form <pre class="prettyprint"> <b>GET</b> /api/v1/forms/<code>{pk}</code>/labels </pre> > Request > > curl -X GET https://ona.io/api/v1/forms/28058/labels > Response > > ["old", "smart", "clean house"] ## Tag forms A `POST` payload of parameter `tags` with a comma separated list of tags. Examples - `animal fruit denim` - space delimited, no commas - `animal, fruit denim` - comma delimited <pre class="prettyprint"> <b>POST</b> /api/v1/forms/<code>{pk}</code>/labels </pre> Payload {"tags": "tag1, tag2"} ## Delete a specific tag <pre class="prettyprint"> <b>DELETE</b> /api/v1/forms/<code>{pk}</code>/labels/<code>tag_name</code> </pre> > Request > > curl -X DELETE \ https://ona.io/api/v1/forms/28058/labels/tag1 > > or to delete the tag "hello world" > > curl -X DELETE \ https://ona.io/api/v1/forms/28058/labels/hello%20world > > Response > > HTTP 200 OK ## Get webform/enketo link <pre class="prettyprint"> <b>GET</b> /api/v1/forms/<code>{pk}</code>/enketo</pre> > Request > > curl -X GET \ https://ona.io/api/v1/forms/28058/enketo > > Response > > {"enketo_url": "https://h6ic6.enketo.org/webform"} > > HTTP 200 OK ## Get form data in xls, csv format. Get form data exported as xls, csv, csv zip, sav zip format. Where: - `pk` - is the form unique identifier - `format` - is the data export format i.e csv, xls, csvzip, savzip Params for the custom xls report - `meta` - the metadata id containing the template url - `token` - the template url <pre class="prettyprint"> <b>GET</b> /api/v1/forms/{pk}.{format}</code> </pre> > Example > > curl -X GET https://ona.io/api/v1/forms/28058.xls > Binary file export of the format specified is returned as the response for >the download. > > Response > > HTTP 200 OK > Example 2 Custom XLS reports (beta) > > curl -X GET https://ona.io/api/v1/forms/28058.xls?meta=12121 > or > curl -X GET https://ona.io/api/v1/forms/28058.xls?token={url} > > XLS file is downloaded > > Response > > HTTP 200 OK ## Get list of public forms <pre class="prettyprint"> <b>GET</b> /api/v1/forms/public </pre> ## Share a form with a specific user You can share a form with a specific user by `POST` a payload with - `username` of the user you want to share the form with and - `role` you want the user to have on the form. Available roles are `readonly`, `dataentry`, `editor`, `manager`. <pre class="prettyprint"> <b>POST</b> /api/v1/forms/<code>{pk}</code>/share </pre> > Example > > curl -X POST -d '{"username": "******", "role": "readonly"}' \ https://ona.io/api/v1/forms/123.json > Response > > HTTP 204 NO CONTENT ## Clone a form to a specific user account You can clone a form to a specific user account using `GET` with - `username` of the user you want to clone the form to <pre class="prettyprint"> <b>GET</b> /api/v1/forms/<code>{pk}</code>/clone </pre> > Example > > curl -X GET https://ona.io/api/v1/forms/123/clone \ -d username=alice > Response > > HTTP 201 CREATED > { > "url": "https://ona.io/api/v1/forms/124", > "formid": 124, > "uuid": "853196d7d0a74bca9ecfadbf7e2f5c1e", > "id_string": "Birds_cloned_1", > "sms_id_string": "Birds_cloned_1", > "title": "Birds_cloned_1", > ... > } ## Import CSV data to existing form - `csv_file` a valid csv file with exported \ data (instance/submission per row) <pre class="prettyprint"> <b>GET</b> /api/v1/forms/<code>{pk}</code>/csv_import </pre> > Example > > curl -X POST https://ona.io/api/v1/forms/123/csv_import \ -F csv_file=@/path/to/csv_import.csv > > Response > > HTTP 200 OK > { > "additions": 9, > "updates": 0 > } """ renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [ renderers.XLSRenderer, renderers.XLSXRenderer, renderers.CSVRenderer, renderers.CSVZIPRenderer, renderers.SAVZIPRenderer, renderers.SurveyRenderer ] queryset = XForm.objects.all() default_response_headers = last_modified_header( get_date(XForm.objects.last(), 'modified')) serializer_class = XFormSerializer lookup_field = 'pk' extra_lookup_fields = None permission_classes = [ XFormPermissions, ] updatable_fields = set(('description', 'downloadable', 'require_auth', 'shared', 'shared_data', 'title')) filter_backends = (filters.AnonDjangoObjectPermissionFilter, filters.TagFilter, filters.XFormOwnerFilter) public_forms_endpoint = 'public' def create(self, request, *args, **kwargs): try: owner = _get_owner(request) except ValidationError as e: return Response({'message': e.messages[0]}, status=status.HTTP_400_BAD_REQUEST) survey = utils.publish_xlsform(request, owner) if isinstance(survey, XForm): xform = XForm.objects.get(pk=survey.pk) serializer = XFormSerializer(xform, context={'request': request}) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) return Response(survey, status=status.HTTP_400_BAD_REQUEST) @action(methods=['GET']) def form(self, request, format='json', **kwargs): form = self.get_object() if format not in ['json', 'xml', 'xls']: return HttpResponseBadRequest('400 BAD REQUEST', content_type='application/json', status=400) return response_for_format(form, format=format) @action(methods=['GET']) def enketo(self, request, **kwargs): self.object = self.get_object() form_url = _get_form_url(request, self.object.user.username) data = {'message': _(u"Enketo not properly configured.")} http_status = status.HTTP_400_BAD_REQUEST try: url = enketo_url(form_url, self.object.id_string) except EnketoError: pass else: if url: http_status = status.HTTP_200_OK data = {"enketo_url": url} return Response(data, http_status) def retrieve(self, request, *args, **kwargs): lookup_field = self.lookup_field lookup = self.kwargs.get(lookup_field) if lookup == self.public_forms_endpoint: self.object_list = self._get_public_forms_queryset() page = self.paginate_queryset(self.object_list) if page is not None: serializer = self.get_pagination_serializer(page) else: serializer = self.get_serializer(self.object_list, many=True) return Response(serializer.data) xform = self.get_object() export_type = kwargs.get('format') query = request.GET.get("query", {}) token = request.GET.get('token') meta = request.GET.get('meta') if export_type is None or export_type in ['json']: # perform default viewset retrieve, no data export return super(XFormViewSet, self).retrieve(request, *args, **kwargs) return custom_response_handler(request, xform, query, export_type, token, meta) @action(methods=['POST']) def share(self, request, *args, **kwargs): self.object = self.get_object() data = {} for key, val in request.DATA.iteritems(): data[key] = val data.update({'xform': self.object.pk}) serializer = ShareXFormSerializer(data=data) if serializer.is_valid(): serializer.save() else: return Response(data=serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(status=status.HTTP_204_NO_CONTENT) @action(methods=['GET']) def clone(self, request, *args, **kwargs): self.object = self.get_object() data = {'xform': self.object.pk, 'username': request.DATA['username']} serializer = CloneXFormSerializer(data=data) if serializer.is_valid(): clone_to_user = User.objects.get(username=data['username']) if not request.user.has_perm('can_add_xform', clone_to_user.profile): raise exceptions.PermissionDenied( detail=_(u"User %(user)s has no permission to add " "xforms to account %(account)s" % { 'user': request.user.username, 'account': data['username'] })) xform = serializer.save() serializer = XFormSerializer(xform.cloned_form, context={'request': request}) return Response(data=serializer.data, status=status.HTTP_201_CREATED) return Response(data=serializer.errors, status=status.HTTP_400_BAD_REQUEST) @detail_route(methods=['POST']) def csv_import(self, request, *args, **kwargs): """ Endpoint for CSV data imports Calls :py:func:`onadata.libs.utils.csv_import.submit_csv` passing with the `request.FILES.get('csv_file')` upload for import. """ resp = submit_csv(request.user.username, self.get_object(), request.FILES.get('csv_file')) return Response(data=resp, status=status.HTTP_200_OK if resp.get('error') is None else status.HTTP_400_BAD_REQUEST) def partial_update(self, request, *args, **kwargs): try: owner = _get_owner(request) except ValidationError as e: return Response({'message': e.messages[0]}, status=status.HTTP_400_BAD_REQUEST) self.object = self.get_object() # updating the file if request.FILES: return _try_update_xlsform(request, self.object, owner) return super(XFormViewSet, self).partial_update(request, *args, **kwargs)
class DataViewSet(AnonymousUserPublicFormsMixin, ModelViewSet): """ This endpoint provides access to submitted data in JSON format. Where: * `pk` - the form unique identifier * `dataid` - submission data unique identifier * `owner` - username of the owner(user/organization) of the data point ## GET JSON List of data end points Lists the data endpoints accessible to requesting user, for anonymous access a list of public data endpoints is returned. <pre class="prettyprint"> <b>GET</b> /api/v1/data </pre> > Example > > curl -X GET https://ona.io/api/v1/data > Response > > [{ > "id": 4240, > "id_string": "dhis2form" > "title": "dhis2form" > "description": "dhis2form" > "url": "https://ona.io/api/v1/data/4240" > }, > ... > ] ## Download data in `csv` format <pre class="prettyprint"> <b>GET</b> /api/v1/data.csv</pre> > > curl -O https://ona.io/api/v1/data.csv ## GET JSON List of data end points filter by owner Lists the data endpoints accessible to requesting user, for the specified `owner` as a query parameter. <pre class="prettyprint"> <b>GET</b> /api/v1/data?<code>owner</code>=<code>owner_username</code> </pre> > Example > > curl -X GET https://ona.io/api/v1/data?owner=ona ## Get Submitted data for a specific form Provides a list of json submitted data for a specific form. <pre class="prettyprint"> <b>GET</b> /api/v1/data/<code>{pk}</code></pre> > Example > > curl -X GET https://ona.io/api/v1/data/22845 > Response > > [ > { > "_id": 4503, > "_bamboo_dataset_id": "", > "_deleted_at": null, > "expense_type": "service", > "_xform_id_string": "exp", > "_geolocation": [ > null, > null > ], > "end": "2013-01-03T10:26:25.674+03", > "start": "2013-01-03T10:25:17.409+03", > "expense_date": "2011-12-23", > "_status": "submitted_via_web", > "today": "2013-01-03", > "_uuid": "2e599f6fe0de42d3a1417fb7d821c859", > "imei": "351746052013466", > "formhub/uuid": "46ea15e2b8134624a47e2c4b77eef0d4", > "kind": "monthly", > "_submission_time": "2013-01-03T02:27:19", > "required": "yes", > "_attachments": [], > "item": "Rent", > "amount": "35000.0", > "deviceid": "351746052013466", > "subscriberid": "639027...60317" > }, > { > .... > "subscriberid": "639027...60317" > } > ] ## Get a single data submission for a given form Get a single specific submission json data providing `pk` and `dataid` as url path parameters, where: * `pk` - is the identifying number for a specific form * `dataid` - is the unique id of the data, the value of `_id` or `_uuid` <pre class="prettyprint"> <b>GET</b> /api/v1/data/<code>{pk}</code>/<code>{dataid}</code></pre> > Example > > curl -X GET https://ona.io/api/v1/data/22845/4503 > Response > > { > "_id": 4503, > "_bamboo_dataset_id": "", > "_deleted_at": null, > "expense_type": "service", > "_xform_id_string": "exp", > "_geolocation": [ > null, > null > ], > "end": "2013-01-03T10:26:25.674+03", > "start": "2013-01-03T10:25:17.409+03", > "expense_date": "2011-12-23", > "_status": "submitted_via_web", > "today": "2013-01-03", > "_uuid": "2e599f6fe0de42d3a1417fb7d821c859", > "imei": "351746052013466", > "formhub/uuid": "46ea15e2b8134624a47e2c4b77eef0d4", > "kind": "monthly", > "_submission_time": "2013-01-03T02:27:19", > "required": "yes", > "_attachments": [], > "item": "Rent", > "amount": "35000.0", > "deviceid": "351746052013466", > "subscriberid": "639027...60317" > }, > { > .... > "subscriberid": "639027...60317" > } > ] ## Query submitted data of a specific form Provides a list of json submitted data for a specific form. Use `query` parameter to apply form data specific, see <a href="http://docs.mongodb.org/manual/reference/operator/query/"> http://docs.mongodb.org/manual/reference/operator/query/</a>. For more details see <a href="https://github.com/modilabs/formhub/wiki/Formhub-Access-Points-(API)# api-parameters"> API Parameters</a>. <pre class="prettyprint"> <b>GET</b> /api/v1/data/<code>{pk}</code>?query={"field":"value"}</b> <b>GET</b> /api/v1/data/<code>{pk}</code>?query={"field":{"op": "value"}}"</b> </pre> > Example > > curl -X GET 'https://ona.io/api/v1/data/22845?query={"kind": \ "monthly"}' > curl -X GET 'https://ona.io/api/v1/data/22845?query={"date": \ {"gt$": "2014-09-29T01:02:03+0000"}}' > Response > > [ > { > "_id": 4503, > "_bamboo_dataset_id": "", > "_deleted_at": null, > "expense_type": "service", > "_xform_id_string": "exp", > "_geolocation": [ > null, > null > ], > "end": "2013-01-03T10:26:25.674+03", > "start": "2013-01-03T10:25:17.409+03", > "expense_date": "2011-12-23", > "_status": "submitted_via_web", > "today": "2013-01-03", > "_uuid": "2e599f6fe0de42d3a1417fb7d821c859", > "imei": "351746052013466", > "formhub/uuid": "46ea15e2b8134624a47e2c4b77eef0d4", > "kind": "monthly", > "_submission_time": "2013-01-03T02:27:19", > "required": "yes", > "_attachments": [], > "item": "Rent", > "amount": "35000.0", > "deviceid": "351746052013466", > "subscriberid": "639027...60317" > }, > { > .... > "subscriberid": "639027...60317" > } > ] ## Query submitted data of a specific form using Tags Provides a list of json submitted data for a specific form matching specific tags. Use the `tags` query parameter to filter the list of forms, `tags` should be a comma separated list of tags. <pre class="prettyprint"> <b>GET</b> /api/v1/data?<code>tags</code>=<code>tag1,tag2</code></pre> <pre class="prettyprint"> <b>GET</b> /api/v1/data/<code>{pk}</code>?<code>tags\ </code>=<code>tag1,tag2</code></pre> > Example > > curl -X GET https://ona.io/api/v1/data/22845?tags=monthly ## Tag a submission data point A `POST` payload of parameter `tags` with a comma separated list of tags. Examples - `animal fruit denim` - space delimited, no commas - `animal, fruit denim` - comma delimited <pre class="prettyprint"> <b>POST</b> /api/v1/data/<code>{pk}</code>/<code>{dataid}</code>/labels</pre> Payload {"tags": "tag1, tag2"} ## Delete a specific tag from a submission <pre class="prettyprint"> <b>DELETE</b> /api/v1/data/<code>{pk}</code>/<code>\ {dataid}</code>/labels/<code>tag_name</code></pre> > Request > > curl -X DELETE \ https://ona.io/api/v1/data/28058/20/labels/tag1 or to delete the tag "hello world" > > curl -X DELETE \ https://ona.io/api/v1/data/28058/20/labels/hello%20world > > Response > > HTTP 200 OK ## Get list of public data endpoints <pre class="prettyprint"> <b>GET</b> /api/v1/data/public </pre> > Example > > curl -X GET https://ona.io/api/v1/data/public > Response > > [{ > "id": 4240, > "id_string": "dhis2form" > "title": "dhis2form" > "description": "dhis2form" > "url": "https://ona.io/api/v1/data/4240" > }, > ... > ] ## Get enketo edit link for a submission instance <pre class="prettyprint"> <b>GET</b> /api/v1/data/<code>{pk}</code>/<code>{dataid}</code>/enketo </pre> > Example > > curl -X GET https://ona.io/api/v1/data/28058/20/enketo?return_url=url > Response > {"url": "https://hmh2a.enketo.formhub.org"} > > ## Delete a specific submission instance Delete a specific submission in a form <pre class="prettyprint"> <b>DELETE</b> /api/v1/data/<code>{pk}</code>/<code>{dataid}</code> </pre> > Example > > curl -X DELETE https://ona.io/api/v1/data/28058/20 > Response > > HTTP 204 No Content > > """ renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [ renderers.XLSRenderer, renderers.XLSXRenderer, renderers.CSVRenderer, renderers.CSVZIPRenderer, renderers.SAVZIPRenderer, renderers.SurveyRenderer ] filter_backends = (filters.AnonDjangoObjectPermissionFilter, filters.XFormOwnerFilter) serializer_class = DataSerializer permission_classes = (XFormPermissions, ) lookup_field = 'pk' lookup_fields = ('pk', 'dataid') extra_lookup_fields = None public_data_endpoint = 'public' queryset = XForm.objects.all() default_response_headers = last_modified_header( get_date(XForm.objects.last(), 'modified')) def get_serializer_class(self): pk_lookup, dataid_lookup = self.lookup_fields pk = self.kwargs.get(pk_lookup) dataid = self.kwargs.get(dataid_lookup) if pk is not None and dataid is None \ and pk != self.public_data_endpoint: serializer_class = DataListSerializer elif pk is not None and dataid is not None: serializer_class = DataInstanceSerializer else: serializer_class = \ super(DataViewSet, self).get_serializer_class() return serializer_class def get_object(self, queryset=None): obj = super(DataViewSet, self).get_object(queryset) pk_lookup, dataid_lookup = self.lookup_fields pk = self.kwargs.get(pk_lookup) dataid = self.kwargs.get(dataid_lookup) if pk is not None and dataid is not None: try: int(dataid) except ValueError: raise ParseError( _(u"Invalid dataid %(dataid)s" % {'dataid': dataid})) obj = get_object_or_404(Instance, pk=dataid, xform__pk=pk) return obj def _get_public_forms_queryset(self): return XForm.objects.filter(Q(shared=True) | Q(shared_data=True)) def _filtered_or_shared_qs(self, qs, pk): filter_kwargs = {self.lookup_field: pk} qs = qs.filter(**filter_kwargs) if not qs: filter_kwargs['shared_data'] = True qs = XForm.objects.filter(**filter_kwargs) if not qs: raise Http404(_(u"No data matches with given query.")) return qs def filter_queryset(self, queryset, view=None): qs = super(DataViewSet, self).filter_queryset(queryset) pk = self.kwargs.get(self.lookup_field) tags = self.request.QUERY_PARAMS.get('tags', None) if tags and isinstance(tags, six.string_types): tags = tags.split(',') qs = qs.filter(tags__name__in=tags).distinct() if pk: try: int(pk) except ValueError: if pk == self.public_data_endpoint: qs = self._get_public_forms_queryset() else: raise ParseError(_(u"Invalid pk %(pk)s" % {'pk': pk})) else: qs = self._filtered_or_shared_qs(qs, pk) return qs @action(methods=['GET', 'POST', 'DELETE'], extra_lookup_fields=[ 'label', ]) def labels(self, request, *args, **kwargs): http_status = status.HTTP_400_BAD_REQUEST instance = self.get_object() self.headers = merge_dicts( self.headers, last_modified_header(get_date(instance, 'modified'))) if request.method == 'POST': if add_tags_to_instance(request, instance): http_status = status.HTTP_201_CREATED tags = instance.tags label = kwargs.get('label', None) if request.method == 'GET' and label: data = [ tag['name'] for tag in tags.filter(name=label).values('name') ] elif request.method == 'DELETE' and label: count = tags.count() tags.remove(label) # Accepted, label does not exist hence nothing removed http_status = status.HTTP_200_OK if count == tags.count() \ else status.HTTP_404_NOT_FOUND data = list(tags.names()) else: data = list(tags.names()) if request.method == 'GET': http_status = status.HTTP_200_OK return Response(data, status=http_status) @action(methods=['GET']) def enketo(self, request, *args, **kwargs): self.object = self.get_object() data = {} if isinstance(self.object, XForm): raise ParseError(_(u"Data id not provided.")) elif (isinstance(self.object, Instance)): self.headers = merge_dicts( self.headers, last_modified_header(get_date(self.object, 'modified'))) if request.user.has_perm("change_xform", self.object.xform): return_url = request.QUERY_PARAMS.get('return_url') if not return_url: raise ParseError(_(u"return_url not provided.")) try: data["url"] = get_enketo_edit_url(request, self.object, return_url) except EnketoError as e: data['detail'] = "{}".format(e) else: raise PermissionDenied(_(u"You do not have edit permissions.")) return Response(data=data) def destroy(self, request, *args, **kwargs): self.object = self.get_object() if isinstance(self.object, XForm): raise ParseError(_(u"Data id not provided.")) elif isinstance(self.object, Instance): if request.user.has_perm("delete_xform", self.object.xform): self.object.delete() else: raise PermissionDenied( _(u"You do not have delete " u"permissions.")) return Response(status=status.HTTP_204_NO_CONTENT) def retrieve(self, request, *args, **kwargs): data_id = str(kwargs.get('dataid')) _format = kwargs.get('format') if not data_id.isdigit(): raise ParseError(_(u"Data ID should be an integer")) try: instance = Instance.objects.get(pk=data_id) self.headers = merge_dicts( self.headers, last_modified_header(get_date(instance, 'modified'))) if _format == 'json' or _format is None: return Response(instance.json) elif _format == 'xml': return Response(instance.xml) else: raise ParseError( _(u"'%(_format)s' format unknown or not implemented!" % {'_format': _format})) except Instance.DoesNotExist: raise ParseError( _(u"data with id '%(data_id)s' not found!" % {'data_id': data_id})) def list(self, request, *args, **kwargs): lookup_field = self.lookup_field lookup = self.kwargs.get(lookup_field) if lookup_field not in kwargs.keys(): self.object_list = self.filter_queryset(self.get_queryset()) serializer = self.get_serializer(self.object_list, many=True) return Response(serializer.data) if lookup == self.public_data_endpoint: self.object_list = self._get_public_forms_queryset() page = self.paginate_queryset(self.object_list) if page is not None: serializer = self.get_pagination_serializer(page) else: serializer = self.get_serializer(self.object_list, many=True) return Response(serializer.data) xform = self.get_object() query = request.GET.get("query", {}) export_type = kwargs.get('format') if export_type is None or export_type in ['json']: # perform default viewset retrieve, no data export return super(DataViewSet, self).list(request, *args, **kwargs) return custom_response_handler(request, xform, query, export_type)
class UserProfileViewSet(ObjectLookupMixin, ModelViewSet): """ List, Retrieve, Update, Create/Register users. ## Register a new User <pre class="prettyprint"><b>POST</b> /api/v1/profiles</pre> > Example > > { > "username": "******", > "name": "Demo User", > "email": "*****@*****.**", > "city": "Kisumu", > "country": "KE", > ... > } ## List User Profiles <pre class="prettyprint"><b>GET</b> /api/v1/profiles</pre> > Example > > curl -X GET https://ona.io/api/v1/profiles > Response > > [ > { > "url": "https://ona.io/api/v1/profiles/demo", > "username": "******", > "name": "Demo User", > "email": "*****@*****.**", > "city": "", > "country": "", > "organization": "", > "website": "", > "twitter": "", > "gravatar": "https://secure.gravatar.com/avatar/xxxxxx", > "require_auth": false, > "user": "******" > "metadata": {}, > "joined_on": "2014-11-10T14:22:20.394Z" > }, > { > ...}, ... > ] ## Retrieve User Profile Information <pre class="prettyprint"><b>GET</b> /api/v1/profiles/{username}</pre> <pre class="prettyprint"><b>GET</b> /api/v1/profiles/{pk}</pre> > Example > > curl -X GET https://ona.io/api/v1/profiles/demo > Response > > { > "url": "https://ona.io/api/v1/profiles/demo", > "username": "******", > "name": "Demo User", > "email": "*****@*****.**", > "city": "", > "country": "", > "organization": "", > "website": "", > "twitter": "", > "gravatar": "https://secure.gravatar.com/avatar/xxxxxx", > "require_auth": false, > "user": "******" > "metadata": {}, > "joined_on": "2014-11-10T14:22:20.394Z" ## Partial updates of User Profile Information Properties of the UserProfile can be updated using `PATCH` http method. Payload required is for properties that are to be changed in JSON, for example, `{"country": "KE"}` will set the country to `KE`. <pre class="prettyprint"><b>PATCH</b> /api/v1/profiles/{username}</pre> > Example > > \ curl -X PATCH -d '{"country": "KE"}' https://ona.io/api/v1/profiles/demo \ -H "Content-Type: application/json" > Response > > { > "url": "https://ona.io/api/v1/profiles/demo", > "username": "******", > "name": "Demo User", > "email": "*****@*****.**", > "city": "", > "country": "KE", > "organization": "", > "website": "", > "twitter": "", > "gravatar": "https://secure.gravatar.com/avatar/xxxxxx", > "require_auth": false, > "user": "******" > "metadata": {}, > "joined_on": "2014-11-10T14:22:20.394Z" > } ## Change authenticated user's password > Example > > curl -X POST -d current_password=password1 -d new_password=password2\ https://ona.io/api/v1/profile/demouser/change_password > Response: > > HTTP 200 OK """ queryset = UserProfile.objects.exclude(user__pk=settings.ANONYMOUS_USER_ID) default_response_headers = last_modified_header( get_date(UserProfile.objects.last(), 'joined')) serializer_class = UserProfileSerializer lookup_field = 'user' permission_classes = [UserProfilePermissions] ordering = ('user__username', ) def get_object(self, queryset=None): """Lookup user profile by pk or username""" if self.kwargs.get(self.lookup_field, None) is None: raise ParseError('Expected URL keyword argument `%s`.' % self.lookup_field) if queryset is None: queryset = self.filter_queryset(self.get_queryset()) serializer = self.get_serializer() lookup_field = self.lookup_field if self.lookup_field in serializer.get_fields(): k = serializer.get_fields()[self.lookup_field] if isinstance(k, serializers.HyperlinkedRelatedField): lookup_field = '%s__%s' % (self.lookup_field, k.lookup_field) lookup = self.kwargs[self.lookup_field] filter_kwargs = {lookup_field: lookup} try: pk = int(lookup) except (TypeError, ValueError): pass else: filter_kwargs = {'user__pk': pk} obj = get_object_or_404(queryset, **filter_kwargs) # May raise a permission denied self.check_object_permissions(self.request, obj) return obj @action(methods=['POST']) def change_password(self, request, *args, **kwargs): user_profile = self.get_object() current_password = request.DATA.get('current_password', None) new_password = request.DATA.get('new_password', None) if new_password: if user_profile.user.check_password(current_password): user_profile.user.set_password(new_password) user_profile.user.save() return Response(status=status.HTTP_200_OK) return Response(status=status.HTTP_400_BAD_REQUEST)