Skip to content

Commit

Permalink
feat: log new submissions
Browse files Browse the repository at this point in the history
  • Loading branch information
rgraber committed Jan 14, 2025
1 parent 8e8d6bb commit acba3db
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 20 deletions.
18 changes: 18 additions & 0 deletions kobo/apps/audit_log/migrations/0016_enable_null_user_uid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.15 on 2025-01-10 19:32

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('audit_log', '0015_add_submission_audit_actions'),
]

operations = [
migrations.AlterField(
model_name='auditlog',
name='user_uid',
field=models.CharField(db_index=True, max_length=22, null=True),
),
]
9 changes: 6 additions & 3 deletions kobo/apps/audit_log/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from kpi.fields.kpi_uid import UUID_LENGTH
from kpi.models import Asset, ImportTask
from kpi.utils.log import logging
from kpi.utils.object_permission import get_database_user

ANONYMOUS_USER_PERMISSION_ACTIONS = {
# key: (permission, granting?), value: ph log action
Expand Down Expand Up @@ -396,6 +397,8 @@ def create_from_request(cls, request: WSGIRequest):
'submission-validation-statuses': cls._create_from_submission_request,
'submission-validation-status': cls._create_from_submission_request,
'assetsnapshot-submission-alias': cls._create_from_submission_request,
'submissions': cls._create_from_submission_request,
'submissions-list': cls._create_from_submission_request,
}
url_name = request.resolver_match.url_name
method = url_name_to_action.get(url_name, None)
Expand Down Expand Up @@ -608,7 +611,7 @@ def _create_from_submission_request(cls, request):
instances: dict[int:SubmissionUpdate] = getattr(request, 'instances', {})
logs = []
url_name = request.resolver_match.url_name

user = get_database_user(request.user)
for instance in instances.values():
if instance.action == 'add':
action = AuditAction.ADD_SUBMISSION
Expand All @@ -628,10 +631,10 @@ def _create_from_submission_request(cls, request):

logs.append(
ProjectHistoryLog(
user=request.user,
user=user,
object_id=request.asset.id,
action=action,
user_uid=request.user.extra_details.uid,
user_uid=user.extra_details.uid,
metadata=metadata,
)
)
Expand Down
15 changes: 1 addition & 14 deletions kobo/apps/audit_log/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,7 @@ def get_date_created(self, audit_log):
return audit_log['date_created'].strftime('%Y-%m-%dT%H:%M:%SZ')


class ProjectHistoryLogSerializer(serializers.ModelSerializer):
user = serializers.HyperlinkedRelatedField(
queryset=get_user_model().objects.all(),
lookup_field='username',
view_name='user-kpi-detail',
)
date_created = serializers.SerializerMethodField()
username = serializers.SerializerMethodField()
class ProjectHistoryLogSerializer(AuditLogSerializer):

class Meta:
model = ProjectHistoryLog
Expand All @@ -94,9 +87,3 @@ class Meta:
'metadata',
'date_created',
)

def get_date_created(self, audit_log):
return audit_log.date_created.strftime('%Y-%m-%dT%H:%M:%SZ')

def get_username(self, audit_log):
return audit_log.user.username
5 changes: 5 additions & 0 deletions kobo/apps/audit_log/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,13 @@ def add_instance_to_request(instance, created, **kwargs):
request = get_current_request()
if request is None:
return
if getattr(instance.asset.asset, 'id', None) is None:
# if an XForm doesn't have a real associated Asset, ignore it
return
if getattr(request, 'instances', None) is None:
request.instances = {}
if getattr(request, 'asset', None) is None:
request.asset = instance.asset.asset
username = instance.user.username if instance.user else None
request.instances.update(
{
Expand Down
53 changes: 53 additions & 0 deletions kobo/apps/audit_log/tests/test_project_history_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import json
import uuid
from unittest.mock import patch
from xml.etree import ElementTree as ET

import jsonschema.exceptions
import responses
from ddt import data, ddt, unpack
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import override_settings
from django.urls import reverse
Expand All @@ -19,6 +21,7 @@
from kobo.apps.audit_log.tests.test_models import BaseAuditLogTestCase
from kobo.apps.hook.models import Hook
from kobo.apps.kobo_auth.shortcuts import User
from kobo.apps.openrosa.libs.utils.logger_tools import dict2xform
from kpi.constants import (
ASSET_TYPE_TEMPLATE,
CLONE_ARG_NAME,
Expand Down Expand Up @@ -1660,3 +1663,53 @@ def test_multiple_submision_validation_statuses(self):
self._check_common_metadata(log2.metadata, PROJECT_HISTORY_LOG_PROJECT_SUBTYPE)
self.assertEqual(log2.action, AuditAction.MODIFY_SUBMISSION)
self.assertEqual(log2.metadata['submission']['status'], 'On Hold')

@data(
(True, False),
(False, True),
(False, False),
)
@unpack
def test_add_submission(self, anonymous, v1):
uuid_ = uuid.uuid4()
self.asset.deploy(backend='mock')
submission_data = {
'q1': 'answer',
'q2': 'answer',
'meta/instanceID': f'uuid:{uuid_}',
'formhub/uuid': self.asset.deployment.xform.uuid,
'_uuid': str(uuid_),
}
xml = ET.fromstring(
dict2xform(submission_data, self.asset.deployment.xform.id_string)
)
xml.tag = self.asset.uid
xml.attrib = {
'id': self.asset.uid,
'version': self.asset.latest_version.uid,
}
endpoint = 'submissions-list' if v1 else 'submissions'
kwargs = {'username': self.user.username} if not v1 else {}
url = reverse(
self._get_endpoint(endpoint),
kwargs=kwargs,
)
data = {'xml_submission_file': SimpleUploadedFile('name.txt', ET.tostring(xml))}
self.asset.assign_perm(perm=PERM_ADD_SUBMISSIONS, user_obj=AnonymousUser())
if not anonymous:
self.client.force_authenticate(user=self.user)

self.client.post(
url,
data=data,
)
# make sure a log was created
logs = ProjectHistoryLog.objects.filter(metadata__asset_uid=self.asset.uid)
self.assertEqual(logs.count(), 1)
log = logs.first()
# check the log has the expected fields and metadata
self.assertEqual(log.object_id, self.asset.id)
self.assertEqual(log.action, AuditAction.ADD_SUBMISSION)
self._check_common_metadata(log.metadata, PROJECT_HISTORY_LOG_PROJECT_SUBTYPE)
username = 'AnonymousUser' if anonymous else self.user.username
self.assertEqual(log.metadata['submission']['submitted_by'], username)
10 changes: 7 additions & 3 deletions kobo/apps/openrosa/apps/api/viewsets/xform_submission_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from rest_framework.renderers import BrowsableAPIRenderer, JSONRenderer
from rest_framework.response import Response

from kobo.apps.audit_log.base_views import AuditLoggedViewSet
from kobo.apps.audit_log.models import AuditType
from kobo.apps.kobo_auth.shortcuts import User
from kobo.apps.openrosa.apps.logger.models import Instance
from kobo.apps.openrosa.libs import filters
Expand All @@ -27,7 +29,6 @@
TokenAuthentication,
)
from kpi.utils.object_permission import get_database_user

from ..utils.rest_framework.viewsets import OpenRosaGenericViewSet
from ..utils.xml import extract_confirmation_message

Expand Down Expand Up @@ -64,7 +65,10 @@ def create_instance_from_json(username, request):


class XFormSubmissionApi(
OpenRosaHeadersMixin, mixins.CreateModelMixin, OpenRosaGenericViewSet
OpenRosaHeadersMixin,
mixins.CreateModelMixin,
OpenRosaGenericViewSet,
AuditLoggedViewSet,
):
"""
Implements OpenRosa Api [FormSubmissionAPI](\
Expand Down Expand Up @@ -124,6 +128,7 @@ class XFormSubmissionApi(
BrowsableAPIRenderer)
serializer_class = SubmissionSerializer
template_name = 'submission.xml'
log_type = AuditType.PROJECT_HISTORY

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand All @@ -150,7 +155,6 @@ def __init__(self, *args, **kwargs):
]

def create(self, request, *args, **kwargs):

username = self.kwargs.get('username')

if self.request.user.is_anonymous:
Expand Down

0 comments on commit acba3db

Please sign in to comment.