예제 #1
0
def clean_check_type_errors(post_data, fields, pk_fields, choice_fields):
    """
    clean posted data and check type errors, add type error messages
    to errors dict returned. This is required because type errors on any
    field prevent triggers from running so we don't get any other errors
    from the datastore_upsert call.

    :param post_data: form data
    :param fields: recombinant fields
    :param pk_fields: list of primary key field ids
    :param choice_fields: {field id: choices, ...}
    :return: cleaned data, errors
    """
    data = {}
    err = {}

    for f in fields:
        f_id = f['datastore_id']
        if not f.get('import_template_include', True):
            continue
        else:
            val = post_data.get(f['datastore_id'], '')
            if isinstance(val, list):
                val = u','.join(val)
            val = canonicalize(
                val,
                f['datastore_type'],
                primary_key=f['datastore_id'] in pk_fields,
                choice_field=f_id in choice_fields)
            if val:
                if f['datastore_type'] in ('money', 'numeric'):
                    try:
                        decimal.Decimal(val)
                    except decimal.InvalidOperation:
                        err[f['datastore_id']] = [_(u'Number required')]
                elif f['datastore_type'] == 'int':
                    try:
                        int(val)
                    except ValueError:
                        err[f['datastore_id']] = [_(u'Integer required')]
            data[f['datastore_id']] = val

    return data, err
예제 #2
0
def get_records(rows, fields, primary_key_fields, choice_fields):
    """
    Truncate/pad empty/missing records to expected row length, canonicalize
    cell content, and return resulting record list.

    :param upload_data: generator producing rows of content
    :type upload_data: generator
    :param fields: collection of fields specified in JSON schema
    :type fields: list or tuple
    :param primary_key_fields: list of field ids making up the PK
    :type primary_key_fields: list of strings
    :param choice_fields: {field_id: 'full'/True/False}
    :type choice_fields: dict

    :return: canonicalized records of specified upload data
    :rtype: tuple of dicts
    """
    records = []
    for n, row in rows:
        # trailing cells might be empty: trim row to fit
        while (row and
                (len(row) > len(fields)) and
                (row[-1] is None or row[-1] == '')):
            row.pop()
        while row and (len(row) < len(fields)):
            row.append(None) # placeholder: canonicalize once only, below

        try:
            records.append(
                (n, dict((
                    f['datastore_id'],
                    canonicalize(
                        v,
                        f['datastore_type'],
                        f['datastore_id'] in primary_key_fields,
                        choice_fields.get(f['datastore_id'], False)))
                for f, v in zip(fields, row))))
        except BadExcelData, e:
            raise BadExcelData(u'Row {0}:'.format(n) + u' ' + e.message)
예제 #3
0
    def update_pd_record(self, owner_org, resource_name, pk):
        pk = [url_part_unescape(p) for p in pk.split(',')]

        lc = LocalCKAN(username=c.user)

        try:
            chromo = h.recombinant_get_chromo(resource_name)
            rcomb = lc.action.recombinant_show(
                owner_org=owner_org,
                dataset_type=chromo['dataset_type'])
            [res] = [r for r in rcomb['resources'] if r['name'] == resource_name]

            check_access(
                'datastore_upsert',
                {'user': c.user, 'auth_user_obj': c.userobj},
                {'resource_id': res['id']})
        except NotAuthorized:
            abort(403, _('Unauthorized'))

        choice_fields = {
            f['datastore_id']: [
                {'value': k, 'label': v} for (k, v) in f['choices']]
            for f in h.recombinant_choice_fields(resource_name)}
        pk_fields = aslist(chromo['datastore_primary_key'])
        pk_filter = dict(zip(pk_fields, pk))

        records = lc.action.datastore_search(
            resource_id=res['id'],
            filters=pk_filter)['records']
        if len(records) == 0:
            abort(404, _('Not found'))
        if len(records) > 1:
            abort(400, _('Multiple records found'))
        record = records[0]

        if request.method == 'POST':
            post_data = parse_params(request.POST, ignore_keys=['save'] + pk_fields)

            if 'cancel' in post_data:
                return redirect(h.url_for(
                    controller='ckanext.recombinant.controller:UploadController',
                    action='preview_table',
                    resource_name=resource_name,
                    owner_org=rcomb['owner_org'],
                    ))

            data = {}
            for f in chromo['fields']:
                f_id = f['datastore_id']
                if not f.get('import_template_include', True):
                    continue
                if f_id in pk_fields:
                    data[f_id] = record[f_id]
                else:
                    val = post_data.get(f['datastore_id'], '')
                    if isinstance(val, list):
                        val = u','.join(val)
                    val = canonicalize(
                        val,
                        f['datastore_type'],
                        primary_key=False,
                        choice_field=f_id in choice_fields)
                    data[f['datastore_id']] = val
            try:
                lc.action.datastore_upsert(
                    resource_id=res['id'],
                    #method='update',    FIXME not raising ValidationErrors
                    records=[data])
            except ValidationError as ve:
                err = {
                    k: [_(e) for e in v]
                    for (k, v) in ve.error_dict['records'][0].items()}
                return render('recombinant/update_pd_record.html',
                    extra_vars={
                        'data': data,
                        'resource_name': resource_name,
                        'chromo_title': chromo['title'],
                        'choice_fields': choice_fields,
                        'pk_fields': pk_fields,
                        'owner_org': rcomb['owner_org'],
                        'errors': err,
                        })

            h.flash_notice(_(u'Record %s Updated') % u','.join(pk) )

            return redirect(h.url_for(
                controller='ckanext.recombinant.controller:UploadController',
                action='preview_table',
                resource_name=resource_name,
                owner_org=rcomb['owner_org'],
                ))

        data = {}
        for f in chromo['fields']:
            if not f.get('import_template_include', True):
                continue
            val = record[f['datastore_id']]
            data[f['datastore_id']] = val

        return render('recombinant/update_pd_record.html',
            extra_vars={
                'data': data,
                'resource_name': resource_name,
                'chromo_title': chromo['title'],
                'choice_fields': choice_fields,
                'pk_fields': pk_fields,
                'owner_org': rcomb['owner_org'],
                'errors': {},
                })
def test_date():
    dt = 'date'
    assert_equal(canonicalize(2019, dt, False), '2019')
    assert_equal(canonicalize(42.0, dt, False), '42.0')
    assert_equal(canonicalize(42.25, dt, False), '42.25')
    assert_equal(canonicalize(0, dt, False), '0')
    assert_equal(canonicalize('2019', dt, False), '2019')
    assert_equal(canonicalize('42.0', dt, False), '42.0')
    assert_equal(canonicalize('42.25', dt, False), '42.25')
    assert_equal(canonicalize('0', dt, False), '0')
    assert_equal(canonicalize(None, dt, False), None)
    assert_equal(canonicalize('', dt, False), None)
    assert_equal(canonicalize('', dt, True), '')
    assert_raises(BadExcelData, canonicalize, '=1+1', dt, False)
    assert_equal(canonicalize(date(2020, 11, 15), dt, False), '2020-11-15')
    assert_equal(canonicalize(datetime(2020, 11, 15), dt, False), '2020-11-15')
    assert_equal(canonicalize('$1,000.50', dt, False), '$1,000.50')
    assert_equal(canonicalize('=TRUE()', dt, False), 'TRUE')
    assert_equal(canonicalize('=FALSE()', dt, False), 'FALSE')
    assert_equal(canonicalize('AB,CD,E', dt, False), 'AB,CD,E')
def test_choice_field():
    assert_equal(canonicalize('C1 ', 'text', False, False), 'C1 ')
    assert_equal(canonicalize('C1 ', 'text', False, True), 'C1')
    assert_equal(canonicalize(' C1: Value', 'text', False, False), ' C1: Value')
    assert_equal(canonicalize(' C1: Value', 'text', False, True), 'C1: Value')
    assert_equal(canonicalize(' C1: Value', 'text', False, 'full'), 'C1')
def test_primary_key():
    dt = 'text'
    assert_equal(canonicalize('OGP-324', dt, True), 'OGP-324')
    assert_equal(canonicalize('\t OGP-324\n', dt, True), 'OGP-324')
    assert_equal(canonicalize('OGP-\r\n\r\n324', dt, True), 'OGP-324')
    assert_equal(canonicalize('OGP- 324', dt, True), 'OGP- 324')
def test_text_array():
    dt = '_text'
    assert_equal(canonicalize(2019, dt, False), ['2019'])
    assert_equal(canonicalize(42.0, dt, False), ['42.0'])
    assert_equal(canonicalize(42.25, dt, False), ['42.25'])
    assert_equal(canonicalize(0, dt, False), ['0'])
    assert_equal(canonicalize('2019', dt, False), ['2019'])
    assert_equal(canonicalize('42.0', dt, False), ['42.0'])
    assert_equal(canonicalize('42.25', dt, False), ['42.25'])
    assert_equal(canonicalize('0', dt, False), ['0'])
    assert_equal(canonicalize(None, dt, False), [])
    assert_equal(canonicalize('', dt, False), [])
    assert_equal(canonicalize('', dt, True), [])
    assert_raises(BadExcelData, canonicalize, '=1+1', dt, False)
    assert_equal(canonicalize(date(2020, 11, 15), dt, False), ['2020-11-15'])
    assert_equal(canonicalize(datetime(2020, 11, 15), dt, False), ['2020-11-15 00:00:00'])
    assert_equal(canonicalize('$1,000.50', dt, False), ['$1', '000.50'])
    assert_equal(canonicalize('=TRUE()', dt, False), ['TRUE'])
    assert_equal(canonicalize('=FALSE()', dt, False), ['FALSE'])
    assert_equal(canonicalize('AB,CD,E', dt, False), ['AB', 'CD', 'E'])
예제 #8
0
    def create_travela(self, id, resource_id):
        lc = LocalCKAN(username=c.user)
        pkg = lc.action.package_show(id=id)
        res = lc.action.resource_show(id=resource_id)
        org = lc.action.organization_show(id=pkg['owner_org'])
        dataset = lc.action.recombinant_show(
            dataset_type='travela', owner_org=org['name'])

        chromo = h.recombinant_get_chromo('travela')
        data = {}
        data_prev = {}
        form_data = {}
        for f in chromo['fields']:
            dirty = request.params.getone(f['datastore_id'])
            data[f['datastore_id']] = canonicalize(dirty, f['datastore_type'], False)
            if f['datastore_id'] + '_prev' in request.params:
                 data_prev[f['datastore_id']] = request.params.getone(f['datastore_id'] + '_prev')
                 form_data[f['datastore_id'] + '_prev'] = data_prev[f['datastore_id']]

        form_data.update(data)

        def error(errors):
            return render('recombinant/resource_edit.html',
                extra_vars={
                    'create_errors': errors,
                    'create_data': form_data,
                    'delete_errors': [],
                    'dataset': dataset,
                    'resource': res,
                    'organization': org,
                    'filters': {},
                    'action': 'edit'})

        try:
            year = int_validator(data['year'], None)
        except Invalid:
            year = None

        if not year:
            return error({'year': [_(u'Invalid year')]})

        response = lc.action.datastore_search(resource_id=resource_id,
            filters={'year': data['year']})
        if response['records']:
            return error({'year': [_(u'Data for this year has already been entered')]})

        response = lc.action.datastore_search(resource_id=resource_id,
            filters={'year': year - 1})
        if response['records']:
            prev = response['records'][0]
            errors = {}
            for p in data_prev:
                if prev[p] != data_prev[p] and prev[p]:
                    errors[p + '_prev'] = [_(u'Does not match previous data "%s"') % prev[p]]
            if errors:
                return error(errors)
        else:
            lc.action.datastore_upsert(resource_id=resource_id,
                method='insert',
                records=[dict(data_prev, year=year - 1)])
            h.flash_success(_("Record for %d added.") % (year - 1))

        lc.action.datastore_upsert(resource_id=resource_id,
            method='insert',
            records=[data])

        h.flash_success(_("Record for %d added.") % year)

        redirect(h.url_for(
            controller='ckanext.recombinant.controller:UploadController',
            action='preview_table',
            resource_name=res['name'],
            owner_org=org['name'],
            ))
예제 #9
0
    def create_travela(self, id, resource_id):
        lc = LocalCKAN(username=c.user)
        pkg = lc.action.package_show(id=id)
        res = lc.action.resource_show(id=resource_id)
        org = lc.action.organization_show(id=pkg['owner_org'])
        dataset = lc.action.recombinant_show(dataset_type='travela',
                                             owner_org=org['name'])

        chromo = h.recombinant_get_chromo('travela')
        data = {}
        data_prev = {}
        form_data = {}
        for f in chromo['fields']:
            dirty = request.params.getone(f['datastore_id'])
            data[f['datastore_id']] = canonicalize(dirty, f['datastore_type'],
                                                   False)
            if f['datastore_id'] + '_prev' in request.params:
                data_prev[f['datastore_id']] = request.params.getone(
                    f['datastore_id'] + '_prev')
                form_data[f['datastore_id'] +
                          '_prev'] = data_prev[f['datastore_id']]

        form_data.update(data)

        def error(errors):
            return render('recombinant/resource_edit.html',
                          extra_vars={
                              'create_errors': errors,
                              'create_data': form_data,
                              'delete_errors': [],
                              'dataset': dataset,
                              'resource': res,
                              'organization': org,
                              'filters': {},
                              'action': 'edit'
                          })

        try:
            year = int_validator(data['year'], None)
        except Invalid:
            year = None

        if not year:
            return error({'year': [_(u'Invalid year')]})

        response = lc.action.datastore_search(resource_id=resource_id,
                                              filters={'year': data['year']})
        if response['records']:
            return error(
                {'year': [_(u'Data for this year has already been entered')]})

        response = lc.action.datastore_search(resource_id=resource_id,
                                              filters={'year': year - 1})
        if response['records']:
            prev = response['records'][0]
            errors = {}
            for p in data_prev:
                if prev[p] != data_prev[p]:
                    errors[p + '_prev'] = [
                        _(u'Does not match previous data "%s"') % prev[p]
                    ]
            if errors:
                return error(errors)
        else:
            lc.action.datastore_upsert(
                resource_id=resource_id,
                method='insert',
                records=[dict(data_prev, year=year - 1)])
            h.flash_success(_("Record for %d added.") % (year - 1))

        lc.action.datastore_upsert(resource_id=resource_id,
                                   method='insert',
                                   records=[data])

        h.flash_success(_("Record for %d added.") % year)

        redirect(
            h.url_for(
                controller='ckanext.recombinant.controller:UploadController',
                action='preview_table',
                resource_name=res['name'],
                owner_org=org['name'],
            ))
예제 #10
0
    def update_pd_record(self, owner_org, resource_name, pk):
        pk = [url_part_unescape(p) for p in pk.split(',')]

        lc = LocalCKAN(username=c.user)

        try:
            chromo = h.recombinant_get_chromo(resource_name)
            rcomb = lc.action.recombinant_show(
                owner_org=owner_org, dataset_type=chromo['dataset_type'])
            [res
             ] = [r for r in rcomb['resources'] if r['name'] == resource_name]

            check_access('datastore_upsert', {
                'user': c.user,
                'auth_user_obj': c.userobj
            }, {'resource_id': res['id']})
        except NotAuthorized:
            abort(403, _('Unauthorized'))

        choice_fields = {
            f['datastore_id']: [{
                'value': k,
                'label': v
            } for (k, v) in f['choices']]
            for f in h.recombinant_choice_fields(resource_name)
        }
        pk_fields = aslist(chromo['datastore_primary_key'])
        pk_filter = dict(zip(pk_fields, pk))

        records = lc.action.datastore_search(resource_id=res['id'],
                                             filters=pk_filter)['records']
        if len(records) == 0:
            abort(404, _('Not found'))
        if len(records) > 1:
            abort(400, _('Multiple records found'))
        record = records[0]

        if request.method == 'POST':
            post_data = parse_params(request.POST,
                                     ignore_keys=['save'] + pk_fields)

            if 'cancel' in post_data:
                return redirect(
                    h.url_for(
                        controller=
                        'ckanext.recombinant.controller:UploadController',
                        action='preview_table',
                        resource_name=resource_name,
                        owner_org=rcomb['owner_org'],
                    ))

            data = {}
            for f in chromo['fields']:
                f_id = f['datastore_id']
                if not f.get('import_template_include', True):
                    continue
                if f_id in pk_fields:
                    data[f_id] = record[f_id]
                else:
                    val = post_data.get(f['datastore_id'], '')
                    if isinstance(val, list):
                        val = u','.join(val)
                    val = canonicalize(val,
                                       f['datastore_type'],
                                       primary_key=False,
                                       choice_field=f_id in choice_fields)
                    data[f['datastore_id']] = val
            try:
                lc.action.datastore_upsert(
                    resource_id=res['id'],
                    #method='update',    FIXME not raising ValidationErrors
                    records=[data])
            except ValidationError as ve:
                err = {
                    k: [_(e) for e in v]
                    for (k, v) in ve.error_dict['records'][0].items()
                }
                return render('recombinant/update_pd_record.html',
                              extra_vars={
                                  'data': data,
                                  'resource_name': resource_name,
                                  'chromo_title': chromo['title'],
                                  'choice_fields': choice_fields,
                                  'pk_fields': pk_fields,
                                  'owner_org': rcomb['owner_org'],
                                  'errors': err,
                              })

            h.flash_notice(_(u'Record %s Updated') % u','.join(pk))

            return redirect(
                h.url_for(
                    controller=
                    'ckanext.recombinant.controller:UploadController',
                    action='preview_table',
                    resource_name=resource_name,
                    owner_org=rcomb['owner_org'],
                ))

        data = {}
        for f in chromo['fields']:
            if not f.get('import_template_include', True):
                continue
            val = record[f['datastore_id']]
            data[f['datastore_id']] = val

        return render('recombinant/update_pd_record.html',
                      extra_vars={
                          'data': data,
                          'resource_name': resource_name,
                          'chromo_title': chromo['title'],
                          'choice_fields': choice_fields,
                          'pk_fields': pk_fields,
                          'owner_org': rcomb['owner_org'],
                          'errors': {},
                      })