Пример #1
0
def test_kuid_persists():
    initial_kuid_1 = 'aaaa1111'
    initial_kuid_2 = 'bbbb2222'

    asset = Asset(
        content={
            'survey': [
                {
                    'type': 'text',
                    'name': 'abc',
                    '$kuid': initial_kuid_1
                },
                {
                    'type': 'text',
                    'name': 'def',
                    '$kuid': initial_kuid_2
                },
            ],
        })
    # kobo_specific_types=True avoids calling _strip_kuids
    # so, can we assume that kuids are supposed to remain?
    content = asset.ordered_xlsform_content(kobo_specific_types=True)
    # kuids are stripped in "kobo_to_xlsform.to_xlsform_structure(...)"

    assert '$kuid' in content['survey'][0]
    assert content['survey'][0].get('$kuid') == initial_kuid_1
    assert '$kuid' in content['survey'][1]
    assert content['survey'][1].get('$kuid') == initial_kuid_2
    def _import_asset(asset, parent_collection=None, asset_type='survey'):
        survey_dict = _csv_to_dict(asset.body)
        obj = {
            'name': asset.name,
            'date_created': asset.date_created,
            'date_modified': asset.date_modified,
            'asset_type': asset_type,
            'owner': user,
        }

        if parent_collection is not None:
            obj['parent'] = parent_collection
            del obj['name']
        new_asset = Asset(**obj)

        _set_auto_field_update(Asset, "date_created", False)
        _set_auto_field_update(Asset, "date_modified", False)
        new_asset.content = survey_dict
        new_asset.date_created = obj['date_created']
        new_asset.date_modified = obj['date_modified']
        new_asset.save()
        _set_auto_field_update(Asset, "date_created", True)
        _set_auto_field_update(Asset, "date_modified", True)

        # Note on the old draft the uid of the new asset
        asset.kpi_asset_uid = new_asset.uid
        asset.save()

        return new_asset
    def _create_cloned_asset(self):
        asset = Asset()
        asset.owner = self.asset.owner
        asset.content = self.asset.content
        asset.save()
        asset.deploy(backend='mock', active=True)
        asset.save()

        return asset
Пример #4
0
def test_remove_empty_expressions():
    a1 = Asset(asset_type='survey', content={})

    c1 = {'survey': [{'relevant': ''}]}
    a1._remove_empty_expressions(c1)
    assert _r1(c1) == {}

    c1 = {'survey': [{'bind': None}]}
    a1._remove_empty_expressions(c1)
    assert _r1(c1) == {}
Пример #5
0
def test_expand_twice():
    a1 = Asset(asset_type='survey', content={'survey': [{'type': 'note',
                                             'label::English': 'english',
                                             'hint::English': 'hint',
                                             }]})
    a1.adjust_content_on_save()
    assert 'translations' in a1.content
    assert len(a1.content['translations']) > 0
    assert 'translated' in a1.content
    assert len(a1.content['translated']) > 0
    assert sorted(a1.content['translated']) == ['hint', 'label']
Пример #6
0
    def setUp(self):
        self.client.login(username="******", password="******")
        self.someuser = User.objects.get(username="******")
        self.asset = a = Asset()
        a.name = 'Two points and one text'
        a.owner = self.someuser
        a.asset_type = 'survey'
        a.content = {
            'survey': [
                {
                    'name': 'geo1',
                    'type': 'geopoint',
                    'label': 'Where were you?'
                },
                {
                    'name': 'geo2',
                    'type': 'geopoint',
                    'label': 'Where are you?'
                },
                {
                    'name': 'text',
                    'type': 'text',
                    'label': 'How are you?'
                },
            ]
        }
        a.save()
        a.deploy(backend='mock', active=True)
        a.save()

        v_uid = a.latest_deployed_version.uid
        self.submissions = [
            {
                '__version__': v_uid,
                'geo1': '10.11 10.12 10.13 10.14',
                'geo2': '10.21 10.22 10.23 10.24',
                'text': 'Tired',
            },
            {
                '__version__': v_uid,
                'geo1': '20.11 20.12 20.13 20.14',
                'geo2': '20.21 20.22 20.23 20.24',
                'text': 'Relieved',
            },
            {
                '__version__': v_uid,
                'geo1': '30.11 30.12 30.13 30.14',
                'geo2': '30.21 30.22 30.23 30.24',
                'text': 'Excited',
            },
        ]
        a.deployment.mock_submissions(self.submissions)
        a.deployment.set_namespace(self.URL_NAMESPACE)
        self.submission_list_url = a.deployment.submission_list_url
Пример #7
0
def test_save_transformations():
    a1 = Asset(asset_type='survey', content={})

    content = color_picker_asset_content()
    a1._standardize(content)
    a1._strip_empty_rows(content)
    a1._assign_kuids(content)
    form_title = a1.pop_setting(content, 'form_title')
    a1._autoname(content)
    assert 'schema' in content
    assert content['translations'] == [None]
    assert form_title == 'color picker'
    assert content['settings'] == {'id_string': 'colorpik'}
Пример #8
0
def _compile_asset_content(content):
    a1 = Asset(asset_type='survey', content={})
    a1._standardize(content)
    a1._strip_empty_rows(content)
    a1._assign_kuids(content)
    form_title = a1.pop_setting(content, 'form_title', 'a backup title')
    a1._autoname(content)
    assert form_title == 'a backup title'
    # at this stage, the save is complete

    a1._expand_kobo_qs(content)
    a1._autoname(content)
    a1._assign_kuids(content)
    return content
Пример #9
0
def nonstandard_asset():
    return Asset(asset_type='survey', content={
        'survey': [
            {'type': 'select_one abc', 'Label': 'select a letter'},
            # todo: handle certain "expand" features after aliases are replaced
            # {'type': 'select1 abc', 'Label': 'select a letter'},
            {},
            {'misc_value': 'gets removed by _strip_empty_rows'},
        ],
        'choices': [
            {'list name': 'abc', 'label': letter}
             for letter in string.ascii_lowercase
        ]
    })
Пример #10
0
def test_autoname_handles_non_latin_labels_with_kobo_score_and_kobo_rank():
    a = Asset(asset_type='survey', content={})
    content = {
        'survey': [
            {
                'type': 'score__row',
                'label': ['नमस्ते']
            },
            {
                'type': 'rank__level',
                'label': ['नमस्ते']
            },
        ]
    }
    a._standardize(content)
    a._strip_empty_rows(content)
    a._assign_kuids(content)
    a._autoname(content)
    for row in content['survey']:
        assert row['$autoname'].startswith('select_one')
Пример #11
0
    def test_export_uid_filter(self):
        assert self.user.username == 'someuser'

        def _create_export_task(asset):
            export_task = ExportTask()
            export_task.user = self.user
            export_task.data = {
                'source': reverse('asset-detail', args=[asset.uid]),
                'type': 'csv'
            }
            messages = defaultdict(list)
            export_task._run_task(messages)
            return export_task

        matching_export = _create_export_task(self.asset)

        # Create a clone and generate an export from it
        excluded_asset = Asset()
        excluded_asset.owner = self.asset.owner
        excluded_asset.content = self.asset.content
        excluded_asset.save()
        excluded_asset.deploy(backend='mock', active=True)
        excluded_asset.save()
        excluded_export = _create_export_task(excluded_asset)

        # Retrieve all the exports unfiltered
        self.client.login(username='******', password='******')
        list_url = reverse(self._get_endpoint('exporttask-list'))
        response = self.client.get(list_url)
        assert response.status_code == status.HTTP_200_OK
        assert response.json()['count'] == 2

        # Retrieve the exports filtered by a single asset uid
        filter_url = f'{list_url}?q=source:{self.asset.uid}'
        response = self.client.get(filter_url)
        assert response.status_code == status.HTTP_200_OK
        response_dict = response.json()
        assert response_dict['count'] == 1
        assert self.asset.uid in response_dict['results'][0]['data']['source']
Пример #12
0
    def test_import_library_bulk_xls(self):
        library_sheet_content = [
            ['block', 'name', 'type', 'label', 'tag:subject:fungus', 'tag:subject:useless'],
            ['mushroom', 'cap', 'text', 'Describe the cap', '1', None],
            ['mushroom', 'gills', 'text', 'Describe the gills', '1', None],
            ['mushroom', 'spores', 'text', 'Describe the spores', '1', None],
            [None, 'non_block', 'acknowledge', 'I am not inside a block!', None, '1'],
            ['mushroom', 'size', 'text', 'Describe the size', '1', None],
            ['mushroom', 'season', 'select_multiple seasons', 'Found during which seasons?', None, None],
            [None, 'also_non_block', 'integer', 'I, too, refuse to join a block!', None, '1'],
        ]
        choices_sheet_content = [
            ['list name', 'name', 'label'],
            ['seasons', 'spring', 'Spring'],
            ['seasons', 'summer', 'Summer'],
            ['seasons', 'fall', 'Fall'],
            ['seasons', 'winter', 'Winter'],
        ]

        content = (
            ('library', library_sheet_content),
            ('choices', choices_sheet_content),
        )
        task_data = self._construct_xls_for_import(
            content, name='Collection created from bulk library import'
        )
        post_url = reverse('api_v2:importtask-list')
        response = self.client.post(post_url, task_data)
        assert response.status_code == status.HTTP_201_CREATED
        detail_response = self.client.get(response.data['url'])
        assert detail_response.status_code == status.HTTP_200_OK
        assert detail_response.data['status'] == 'complete'

        # Transform the XLS sheets and rows into asset content for comparison
        # with the results of the import

        # Discarding the first (`block`) column and any tag columns, combine
        # the first row (headers) with subsequent 'mushroom' rows
        headers = [
            col
            for col in library_sheet_content[0][1:]
            if not col.startswith('tag:')
        ]
        mushroom_block_survey = [
            # We don't have to remove the tag columns from each row, because
            # `zip()` stops once it reaches the end of `headers`
            dict(zip(headers, row[1:])) for row in library_sheet_content[1:]
            if row[0] == 'mushroom'
        ]
        # Transform the choices for the 'mushroom' block into a list of dicts
        mushroom_block_choices = [
            dict(zip(choices_sheet_content[0], row))
            for row in choices_sheet_content[1:]
        ]
        # Create the in-memory only asset, but adjust its contents as if we
        # were saving it to the database
        mushroom_block_asset = Asset(
            content={
                'survey': mushroom_block_survey,
                'choices': mushroom_block_choices,
            }
        )
        mushroom_block_asset.adjust_content_on_save()
        # Similarly create the in-memory assets for the simpler, non-block
        # questions
        non_block_assets = []
        for row in library_sheet_content:
            if row[0] is not None:
                continue
            question_asset = Asset(
                content={'survey': [dict(zip(headers, row[1:]))]}
            )
            question_asset.adjust_content_on_save()
            non_block_assets.append(question_asset)

        # Find the new collection created by the import
        created_details = detail_response.data['messages']['created'][0]
        assert created_details['kind'] == 'collection'
        created_collection = Asset.objects.get(uid=created_details['uid'])
        assert created_collection.name == task_data['name']
        created_children = created_collection.children.order_by('date_created')
        assert len(created_children) == 3

        # Verify the imported block
        created_block = created_children[0]
        assert created_block.asset_type == ASSET_TYPE_BLOCK
        self._assert_assets_contents_equal(created_block, mushroom_block_asset)
        self._assert_assets_contents_equal(
            created_block, mushroom_block_asset, sheet='choices'
        )

        # Verify the imported non-block questions
        created_questions = created_children[1:]
        for q in created_questions:
            assert q.asset_type == ASSET_TYPE_QUESTION
        self._assert_assets_contents_equal(
            created_questions[0], non_block_assets[0]
        )
        self._assert_assets_contents_equal(
            created_questions[1], non_block_assets[1]
        )

        # Check that tags were assigned correctly
        tagged_as_fungus = Asset.objects.filter_by_tag_name(
            'subject:fungus'
        ).filter(parent=created_collection).order_by('date_created')
        assert tagged_as_fungus.count() == 1
        self._assert_assets_contents_equal(
            tagged_as_fungus[0], mushroom_block_asset
        )
        tagged_as_useless = Asset.objects.filter_by_tag_name(
            'subject:useless'
        ).filter(parent=created_collection).order_by('date_created')
        assert tagged_as_useless.count() == 2
        self._assert_assets_contents_equal(
            tagged_as_useless[0], non_block_assets[0]
        )
        self._assert_assets_contents_equal(
            tagged_as_useless[1], non_block_assets[1]
        )
Пример #13
0
    def handle(self, *args, **options):
        if not settings.KOBOCAT_URL or not settings.KOBOCAT_INTERNAL_URL:
            raise ImproperlyConfigured(
                'Both KOBOCAT_URL and KOBOCAT_INTERNAL_URL must be '
                'configured before using this command'
            )
        self._quiet = options.get('quiet')
        username = options.get('username')
        populate_xform_kpi_asset_uid = options.get('populate_xform_kpi_asset_uid')
        users = User.objects.all()
        # Do a basic query just to make sure the ReadOnlyKobocatXForm model is
        # loaded
        if not ReadOnlyKobocatXForm.objects.exists():
            return
        self._print_str('%d total users' % users.count())
        # A specific user or everyone?
        if username:
            users = User.objects.filter(username=username)
        self._print_str('%d users selected' % users.count())

        # We'll be copying the date fields from KC, so don't auto-update them
        _set_auto_field_update(Asset, "date_created", False)
        _set_auto_field_update(Asset, "date_modified", False)

        for user in users:
            # Make sure the user has a token for access to KC's API
            Token.objects.get_or_create(user=user)

            existing_surveys = user.assets.filter(asset_type='survey')

            # Each asset that the user has already deployed to KC should have a
            # form uuid stored in its deployment data
            xform_uuids_to_asset_pks = {}
            for existing_survey in existing_surveys:
                dd = existing_survey._deployment_data
                try:
                    backend_response = dd['backend_response']
                except KeyError:
                    continue
                xform_uuids_to_asset_pks[backend_response['uuid']] = \
                    existing_survey.pk

            xforms = user.xforms.all()
            for xform in xforms:
                try:
                    with transaction.atomic():
                        if xform.uuid not in xform_uuids_to_asset_pks:
                            # This is an orphaned KC form. Build a new asset to
                            # match
                            asset = Asset(asset_type='survey', owner=user)
                            asset.name = _make_name_for_asset(asset, xform)
                        else:
                            asset = Asset.objects.get(
                                pk=xform_uuids_to_asset_pks[xform.uuid])

                        changes = []
                        try:
                            content_changed = _sync_form_content(
                                asset, xform, changes)
                            metadata_changed = _sync_form_metadata(
                                asset, xform, changes)
                        except SyncKCXFormsWarning as e:
                            error_information = [
                                'WARN',
                                user.username,
                                xform.id_string,
                                e.message
                            ]
                            self._print_tabular(*error_information)
                            continue

                        if content_changed or metadata_changed:
                            # preserve the original "asset.content"
                            asset.save(adjust_content=False)
                            # save a new version with standardized content
                            asset.save()
                            if content_changed:
                                asset._mark_latest_version_as_deployed()
                            self._print_tabular(
                                ','.join(changes),
                                user.username,
                                xform.id_string,
                                asset.uid
                            )
                        else:
                            self._print_tabular(
                                'NOOP',
                                user.username,
                                xform.id_string,
                                asset.uid
                            )
                except Exception as e:
                    error_information = [
                        'FAIL',
                        user.username,
                        xform.id_string,
                        repr(e)
                    ]
                    self._print_tabular(*error_information)
                    logging.exception('sync_kobocat_xforms: {}'.format(
                        ', '.join(error_information)))

        _set_auto_field_update(Asset, "date_created", True)
        _set_auto_field_update(Asset, "date_modified", True)

        if populate_xform_kpi_asset_uid:
            call_command('populate_kc_xform_kpi_asset_uid', username=username)
Пример #14
0
 def _new(): return Asset(asset_type='survey', content=fn())
 return _new
Пример #15
0
 def _name_to_autoname(rows):
     s = Asset(asset_type='survey', content={})
     rows = [dict({'type': 'text'}, **row) for row in rows]
     content = {'survey': rows}
     s._autoname(content)
     return [r['$autoname'] for r in content.get('survey', [])]