Exemple #1
0
def patch_xform(request, domain, app_id, unique_form_id):
    patch = request.POST['patch']
    sha1_checksum = request.POST['sha1']
    case_references = _get_case_references(request.POST)

    app = get_app(domain, app_id)
    form = app.get_form(unique_form_id)

    current_xml = form.source
    if hashlib.sha1(current_xml.encode('utf-8')).hexdigest() != sha1_checksum:
        return json_response({'status': 'conflict', 'xform': current_xml})

    dmp = diff_match_patch()
    xform, _ = dmp.patch_apply(dmp.patch_fromText(patch), current_xml)
    save_xform(app, form, xform)
    if "case_references" in request.POST or "references" in request.POST:
        form.case_references = case_references

    response_json = {
        'status': 'ok',
        'sha1': hashlib.sha1(form.source.encode('utf-8')).hexdigest()
    }
    app.save(response_json)
    notify_form_changed(domain, request.couch_user, app_id, unique_form_id)
    return json_response(response_json)
Exemple #2
0
def patch_xform(request, domain, app_id, unique_form_id):
    patch = request.POST['patch']
    sha1_checksum = request.POST['sha1']
    case_references = _get_case_references(request.POST)

    app = get_app(domain, app_id)
    form = app.get_form(unique_form_id)

    current_xml = form.source
    if hashlib.sha1(current_xml.encode('utf-8')).hexdigest() != sha1_checksum:
        return json_response({'status': 'conflict', 'xform': current_xml})

    dmp = diff_match_patch()
    xform, _ = dmp.patch_apply(dmp.patch_fromText(patch), current_xml)
    save_xform(app, form, xform)
    if "case_references" in request.POST or "references" in request.POST:
        form.case_references = case_references

    response_json = {
        'status': 'ok',
        'sha1': hashlib.sha1(form.source.encode('utf-8')).hexdigest()
    }
    app.save(response_json)
    notify_form_changed(domain, request.couch_user, app_id, unique_form_id)
    return json_response(response_json)
Exemple #3
0
def fix_user_props_copy(app, module, form, form_ix, preloads, dry):
    updated = False
    xform = XForm(form.source)
    refs = {xform.resolve_path(ref): prop for ref, prop in preloads.items()}
    for node in xform.model_node.findall("{f}setvalue"):
        if (node.attrib.get('ref') in refs
                and node.attrib.get('event') == "xforms-ready"):
            ref = node.attrib.get('ref')
            value = (node.attrib.get('value') or "").replace(" ", "")
            prop = refs[ref]
            userprop = "#user/" + prop
            if value == get_bad_usercase_path(module, form, prop):
                logger.info("%s setvalue %s -> %s", form_ix, userprop, ref)
                node.attrib["value"] = USERPROP_PREFIX + prop
                updated = True
            elif value != USERPROP_PREFIX + prop:
                logger.warn("%s %s has unexpected value: %r (not %s)", form_ix,
                            ref, value, userprop)
    if updated:
        if dry:
            logger.info(
                "updated setvalues in XML:\n%s",
                "\n".join(line for line in ET.tostring(xform.xml).split("\n")
                          if "setvalue" in line))
        else:
            save_xform(app, form, ET.tostring(xform.xml))
    return updated
    def handle(self, path, app_id, **options):
        if options['deploy'] and not options['user']:
            raise CommandError('Deploy argument requires a user')
        elif options['deploy']:
            user = CouchUser.get_by_username(options['user'])
            if not user:
                raise CommandError(
                    "Couldn't find user with username {}".format(
                        options['user']))

        app = Application.get(app_id)
        for module_dir in os.listdir(path):
            module_index, name = module_dir.split(' - ')
            module = app.get_module(int(module_index))
            for form_name in os.listdir(os.path.join(path, module_dir)):
                form_index, name = form_name.split(' - ')
                form = module.get_form(int(form_index))
                with open(os.path.join(path, module_dir, form_name),
                          'rb') as f:
                    save_xform(app, form, f.read())

        app.save()
        print('successfully updated {}'.format(app.name))
        if options['deploy']:
            # make build and star it
            comment = options.get(
                'comment', 'form changes from {0}'.format(
                    datetime.utcnow().strftime(SERVER_DATETIME_FORMAT_NO_SEC)))
            copy = app.make_build(
                comment=comment,
                user_id=user._id,
            )
            copy.is_released = True
            copy.save(increment_version=False)
            print('successfully released new version')
Exemple #5
0
def migrate_preloads(app, form, preload_items, form_ix, dry):
    xform = XForm(form.source)
    if form.case_references:
        load_refs = form.case_references.load
    else:
        load_refs = {}
        form.case_references = CaseReferences(load=load_refs)
    for hashtag, preloads in preload_items:
        if hashtag == "#case/":
            xform.add_case_preloads(preloads)
        elif hashtag == "#user/":
            xform.add_casedb()
            for nodeset, prop in preloads.items():
                assert '/' not in prop, (app.id, form.unique_id, prop)
                xform.add_setvalue(ref=nodeset, value=USERPROP_PREFIX + prop)
        else:
            raise ValueError("unknown hashtag: " + hashtag)
        for nodeset, prop in preloads.items():
            load_refs.setdefault(nodeset, []).append(hashtag + prop)
            logger.info("%s/%s %s setvalue %s = %s", app.domain, app._id,
                        form_ix, nodeset, hashtag + prop)
    if dry:
        logger.info(
            "setvalue XML: %s",
            " ".join(line.strip()
                     for line in ET.tostring(xform.xml).split("\n")
                     if "setvalue" in line))
    else:
        save_xform(app, form, ET.tostring(xform.xml))
def migrate_preloads(app, form, preload_items, form_ix, dry):
    xform = XForm(form.source)
    if form.case_references:
        load_refs = form.case_references.load
    else:
        load_refs = {}
        form.case_references = CaseReferences(load=load_refs)
    for hashtag, preloads in preload_items:
        if hashtag == "#case/":
            xform.add_case_preloads(preloads)
        elif hashtag == "#user/":
            xform.add_casedb()
            for nodeset, prop in preloads.items():
                assert '/' not in prop, (app.id, form.unique_id, prop)
                xform.add_setvalue(ref=nodeset, value=USERPROP_PREFIX + prop)
        else:
            raise ValueError("unknown hashtag: " + hashtag)
        for nodeset, prop in six.iteritems(preloads):
            load_refs.setdefault(nodeset, []).append(hashtag + prop)
            logger.info("%s/%s %s setvalue %s = %s",
                app.domain, app._id, form_ix, nodeset, hashtag + prop)
    if dry:
        logger.info("setvalue XML: %s", " ".join(line.strip()
            for line in ET.tostring(xform.xml).split("\n")
            if "setvalue" in line))
    else:
        save_xform(app, form, ET.tostring(xform.xml))
def fix_user_props_copy(app, module, form, form_ix, preloads, dry):
    updated = False
    xform = XForm(form.source)
    refs = {xform.resolve_path(ref): prop for ref, prop in six.iteritems(preloads)}
    for node in xform.model_node.findall("{f}setvalue"):
        if (node.attrib.get('ref') in refs
                and node.attrib.get('event') == "xforms-ready"):
            ref = node.attrib.get('ref')
            value = (node.attrib.get('value') or "").replace(" ", "")
            prop = refs[ref]
            userprop = "#user/" + prop
            if value == get_bad_usercase_path(module, form, prop):
                logger.info("%s setvalue %s -> %s", form_ix, userprop, ref)
                node.attrib["value"] = USERPROP_PREFIX + prop
                updated = True
            elif value != USERPROP_PREFIX + prop:
                logger.warn("%s %s has unexpected value: %r (not %s)",
                    form_ix, ref, value, userprop)
    if updated:
        if dry:
            logger.info("updated setvalues in XML:\n%s", "\n".join(line
                for line in ET.tostring(xform.xml).split("\n")
                if "setvalue" in line))
        else:
            save_xform(app, form, ET.tostring(xform.xml))
    return updated
 def handle(self, *args, **options):
     toggle_map = dict([(t.slug, t) for t in all_toggles()])
     domains = [row['key'] for row in Domain.get_all(include_docs=False)]
     for domain in domains:
         if toggle_map['rich_text'].enabled(domain) or toggle_map['experimental_ui'].enabled(domain):
             #logger.info('migrating domain {}'.format(domain))
             apps = get_apps_in_domain(domain, include_remote=False)
             for app in apps:
                 app_dirty = False
                 builder = ParentCasePropertyBuilder(app)
                 relationships = builder.get_parent_type_map(app.get_case_types(), allow_multiple_parents=True)
                 for module in app.modules:
                     for form in module.forms:
                         if form.doc_type == 'Form' and form.requires_case():
                             #logger.info('migrating form {}'.format(form.name.get('en', form.name)))
                             base_case_type = form.get_module().case_type
                             self._replace_in_form(form, relationships, base_case_type, 0)
                             prefixes = re.findall(r'#case/\w+/', form.source)
                             prefixes = set(prefixes)
                             for p in prefixes:
                                 if p != "#case/parent/" and p != "#case/grandparent/":
                                     self._form_error(form, "Unknown prefix remaining: {}".format(p))
                             if options['save']:
                                 try:
                                     save_xform(form.get_app(), form, form.source)
                                     app_dirty = True
                                 except:
                                     self._form_error(form, "Form xml invalid")
                 if app_dirty:
                     app.save()
     logger.info('done with cmitfb_migrate_syntax')
    def handle(self, path, app_id, **options):
        if options['deploy'] and not options['user']:
            raise CommandError('Deploy argument requires a user')
        elif options['deploy']:
            user = CouchUser.get_by_username(options['user'])
            if not user:
                raise CommandError("Couldn't find user with username {}".format(options['user']))

        app = Application.get(app_id)
        for module_dir in os.listdir(path):
            module_index, name = module_dir.split(' - ')
            module = app.get_module(int(module_index))
            for form_name in os.listdir(os.path.join(path, module_dir)):
                form_index, name = form_name.split(' - ')
                form = module.get_form(int(form_index))
                with open(os.path.join(path, module_dir, form_name), 'rb') as f:
                    save_xform(app, form, f.read())

        app.save()
        print('successfully updated {}'.format(app.name))
        if options['deploy']:
            # make build and star it
            comment = options.get('comment', 'form changes from {0}'.format(datetime.utcnow().strftime(SERVER_DATETIME_FORMAT_NO_SEC)))
            copy = app.make_build(
                comment=comment,
                user_id=user._id,
            )
            copy.is_released = True
            copy.save(increment_version=False)
            print('successfully released new version')
Exemple #10
0
 def test_save_xform_does_not_change_xmlns_if_already_unique(self):
     source = self.get_source()
     form = self.form
     save_xform(self.app, form, source.encode('utf-8'))
     self.assertEqual(form.source, source)
     self.assertEqual(form.xmlns, DEFAULT_XMLNS)
     # clear cach because form.xmlns changed in save_xform
     Application.get_xmlns_map.get_cache(self.app).clear()
     self.assertEqual(self.app.get_xmlns_map().get(form.xmlns), [form])
Exemple #11
0
 def test_save_xform_does_not_change_xmlns_if_already_unique(self):
     source = self.get_source()
     form = self.form
     save_xform(self.app, form, source)
     self.assertEqual(form.source, source)
     self.assertEqual(form.xmlns, DEFAULT_XMLNS)
     # clear cach because form.xmlns changed in save_xform
     Application.get_xmlns_map.get_cache(self.app).clear()
     self.assertEqual(self.app.get_xmlns_map().get(form.xmlns), [form])
Exemple #12
0
def migrate_preloads(app, form, preloads):
    xform = XForm(form.source)
    for kwargs in preloads:
        hashtag = kwargs.pop("hashtag")
        xform.add_case_preloads(**kwargs)
        refs = {path: [hashtag + case_property]
                for path, case_property in kwargs["preloads"].iteritems()}
        if form.case_references:
            form.case_references.load.update(refs)
        else:
            form.case_references = CaseReferences(load=refs)
    save_xform(app, form, ET.tostring(xform.xml))
Exemple #13
0
    def test_save_xform_with_conflicting_xmlns_sets_new_xmlns(self):
        source = self.get_source()
        save_xform(self.app, self.form, source)
        self.assertEqual(self.form.source, source)

        # clear cach because form.xmlns changed in save_xform
        Application.get_xmlns_map.get_cache(self.app).clear()
        form2 = self.app.new_form(self.module.id, "Form 2", None)
        form2_xmlns = form2.xmlns
        self.assertNotIn(form2_xmlns, source)
        save_xform(self.app, form2, source)
        self.assertIn(form2_xmlns, form2.source)
        self.assertNotIn(DEFAULT_XMLNS, form2.source)
Exemple #14
0
    def test_save_xform_with_conflicting_xmlns_sets_new_xmlns(self):
        source = self.get_source()
        save_xform(self.app, self.form, source.encode('utf-8'))
        self.assertEqual(self.form.source, source)

        # clear cach because form.xmlns changed in save_xform
        Application.get_xmlns_map.get_cache(self.app).clear()
        form2 = self.app.new_form(self.module.id, "Form 2", None)
        form2_xmlns = form2.xmlns
        self.assertNotIn(form2_xmlns, source)
        save_xform(self.app, form2, source)
        self.assertIn(form2_xmlns, form2.source)
        self.assertNotIn(DEFAULT_XMLNS, form2.source)
Exemple #15
0
    def test_save_xform_with_shadow_form_does_not_change_xmlns(self):
        source = self.get_source()
        module = self.app.add_module(AdvancedModule.new_module('a module', None))
        form = self.app.new_form(self.module.id, "a form", None)
        save_xform(self.app, form, source.encode('utf-8'))
        Application.get_xmlns_map.get_cache(self.app).clear()

        shadow = module.new_shadow_form("shadow form", "en")
        shadow.shadow_parent_form_id = form.unique_id
        self.assertEqual(shadow.shadow_parent_form, form)

        save_xform(self.app, form, source.encode('utf-8'))
        self.assertEqual(form.source, source)
        self.assertEqual(form.xmlns, DEFAULT_XMLNS)
 def register_new_user(self, data):
     reg_form = RegisterWebUserForm(
         data['data'],
         show_number=(
             self.ab.version == ab_tests.NEW_USER_NUMBER_OPTION_SHOW_NUM))
     if reg_form.is_valid():
         self._create_new_account(reg_form)
         try:
             requested_domain = request_new_domain(self.request,
                                                   reg_form,
                                                   is_new_user=True)
             # If user created a form via prelogin demo, create an app for them
             if reg_form.cleaned_data['xform']:
                 lang = 'en'
                 app = Application.new_app(requested_domain,
                                           "Untitled Application")
                 module = Module.new_module(_("Untitled Module"), lang)
                 app.add_module(module)
                 save_xform(app, app.new_form(0, "Untitled Form", lang),
                            reg_form.cleaned_data['xform'])
                 app.save()
                 web_user = WebUser.get_by_username(
                     reg_form.cleaned_data['email'])
                 if web_user:
                     update_hubspot_properties(web_user, {
                         'signup_via_demo': 'yes',
                     })
         except NameUnavailableException:
             # technically, the form should never reach this as names are
             # auto-generated now. But, just in case...
             logging.error(
                 "There as an issue generating a unique domain name "
                 "for a user during new registration.")
             return {
                 'errors': {
                     'project name unavailable': [],
                 }
             }
         return {
             'success': True,
         }
     logging.error(
         "There was an error processing a new user registration form."
         "This shouldn't happen as validation should be top-notch "
         "client-side. Here is what the errors are: {}".format(
             reg_form.errors))
     return {
         'errors': reg_form.errors,
     }
Exemple #17
0
def patch_xform(request, domain, app_id, form_unique_id):
    patch = request.POST['patch']
    sha1_checksum = request.POST['sha1']
    case_references = _get_case_references(request.POST)

    app = get_app(domain, app_id)
    form = app.get_form(form_unique_id)

    conflict = _get_xform_conflict_response(form, sha1_checksum)
    if conflict is not None:
        return conflict

    xml = apply_patch(patch, form.source)

    try:
        xml = save_xform(app, form, xml.encode('utf-8'))
    except XFormException:
        return JsonResponse({'status': 'error'}, status=HttpResponseBadRequest.status_code)

    if "case_references" in request.POST or "references" in request.POST:
        form.case_references = case_references

    response_json = {
        'status': 'ok',
        'sha1': hashlib.sha1(xml).hexdigest()
    }
    app.save(response_json)
    notify_form_changed(domain, request.couch_user, app_id, form_unique_id)
    return JsonResponse(response_json)
Exemple #18
0
def patch_xform(request, domain, app_id, form_unique_id):
    patch = request.POST['patch']
    sha1_checksum = request.POST['sha1']
    case_references = _get_case_references(request.POST)

    app = get_app(domain, app_id)
    form = app.get_form(form_unique_id)

    conflict = _get_xform_conflict_response(form, sha1_checksum)
    if conflict is not None:
        return conflict

    current_xml = form.source
    dmp = diff_match_patch()
    xml, _ = dmp.patch_apply(dmp.patch_fromText(patch), current_xml)
    xml = save_xform(app, form, xml.encode('utf-8'))
    if "case_references" in request.POST or "references" in request.POST:
        form.case_references = case_references

    response_json = {
        'status': 'ok',
        'sha1': hashlib.sha1(xml).hexdigest()
    }
    app.save(response_json)
    notify_form_changed(domain, request.couch_user, app_id, form_unique_id)
    return json_response(response_json)
Exemple #19
0
def patch_xform(request, domain, app_id, form_unique_id):
    patch = request.POST['patch']
    sha1_checksum = request.POST['sha1']
    case_references = _get_case_references(request.POST)

    app = get_app(domain, app_id)
    form = app.get_form(form_unique_id)

    conflict = _get_xform_conflict_response(form, sha1_checksum)
    if conflict is not None:
        return conflict

    current_xml = form.source
    dmp = diff_match_patch()
    xml, _ = dmp.patch_apply(dmp.patch_fromText(patch), current_xml)
    xml = save_xform(app, form, xml.encode('utf-8'))
    if "case_references" in request.POST or "references" in request.POST:
        form.case_references = case_references

    response_json = {
        'status': 'ok',
        'sha1': hashlib.sha1(xml).hexdigest()
    }
    app.save(response_json)
    notify_form_changed(domain, request.couch_user, app_id, form_unique_id)
    return json_response(response_json)
    def migrate_app(self, app_id):
        app = Application.get(app_id)
        modules = [m for m in app.modules if m.module_type == 'basic']

        for module in modules:
            forms = [f for f in module.forms if f.doc_type == 'Form']
            for form in forms:
                preload = form.actions.case_preload.preload
                if preload:
                    xform = XForm(form.source)
                    xform.add_case_preloads(preload)
                    save_xform(app, form, ET.tostring(xform.xml))
                    form.actions.load_from_form = form.actions.case_preload
                    form.actions.case_preload = PreloadAction()

        app.vellum_case_management = True
        app.save()
    def migrate_app(self, app_id):
        app = Application.get(app_id)
        if app.vellum_case_management:
            logger.info('already migrated app {}'.format(app_id))
            return

        modules = [m for m in app.modules if m.module_type == 'basic']
        for module in modules:
            forms = [f for f in module.forms if f.doc_type == 'Form']
            for form in forms:
                preload = form.actions.case_preload.preload
                if preload:
                    xform = XForm(form.source)
                    xform.add_case_preloads(preload)
                    save_xform(app, form, ET.tostring(xform.xml))
                    form.actions.load_from_form = form.actions.case_preload
                    form.actions.case_preload = PreloadAction()

        app.vellum_case_management = True
        app.save()
Exemple #22
0
def patch_xform(request, domain, app_id, unique_form_id):
    patch = request.POST['patch']
    sha1_checksum = request.POST['sha1']

    app = get_app(domain, app_id)
    form = app.get_form(unique_form_id)

    current_xml = form.source
    if hashlib.sha1(current_xml.encode('utf-8')).hexdigest() != sha1_checksum:
        return json_response({'status': 'conflict', 'xform': current_xml})

    dmp = diff_match_patch()
    xform, _ = dmp.patch_apply(dmp.patch_fromText(patch), current_xml)
    save_xform(app, form, xform)
    response_json = {
        'status': 'ok',
        'sha1': hashlib.sha1(form.source.encode('utf-8')).hexdigest()
    }
    app.save(response_json)
    return json_response(response_json)
Exemple #23
0
    def update(self, rows):
        try:
            self._check_for_shadow_form_error()
        except BulkAppTranslationsException as e:
            return [(messages.error, str(e))]

        if not self.itext:
            # This form is empty or malformed. Ignore it.
            return []

        # Setup
        rows = get_unicode_dicts(rows)
        template_translation_el = self._get_template_translation_el()
        self._add_missing_translation_elements_to_itext(
            template_translation_el)
        self._populate_markdown_stats(rows)
        self.msgs = []

        # Skip labels that have no translation provided
        label_ids_to_skip = self._get_label_ids_to_skip(rows)

        # Update the translations
        for lang in self.langs:
            translation_node = self.itext.find("./{f}translation[@lang='%s']" %
                                               lang)
            assert (translation_node.exists())

            for row in rows:
                if row['label'] in label_ids_to_skip:
                    continue
                try:
                    self._add_or_remove_translations(lang, row)
                except BulkAppTranslationsException as e:
                    self.msgs.append((messages.warning, str(e)))

        save_xform(self.app, self.form,
                   etree.tostring(self.xform.xml, encoding='utf-8'))

        return [(t, _('Error in {sheet}: {msg}').format(sheet=self.sheet_name,
                                                        msg=m))
                for (t, m) in self.msgs]
Exemple #24
0
 def handle(self, *args, **options):
     toggle_map = dict([(t.slug, t) for t in all_toggles()])
     domains = [row['key'] for row in Domain.get_all(include_docs=False)]
     for domain in domains:
         if toggle_map['rich_text'].enabled(
                 domain) or toggle_map['experimental_ui'].enabled(domain):
             #logger.info('migrating domain {}'.format(domain))
             apps = get_apps_in_domain(domain, include_remote=False)
             for app in apps:
                 app_dirty = False
                 builder = ParentCasePropertyBuilder(app)
                 relationships = builder.get_parent_type_map(
                     app.get_case_types(), allow_multiple_parents=True)
                 for module in app.modules:
                     for form in module.forms:
                         if form.doc_type == 'Form' and form.requires_case(
                         ):
                             #logger.info('migrating form {}'.format(form.name.get('en', form.name)))
                             base_case_type = form.get_module().case_type
                             self._replace_in_form(form, relationships,
                                                   base_case_type, 0)
                             prefixes = re.findall(r'#case/\w+/',
                                                   form.source)
                             prefixes = set(prefixes)
                             for p in prefixes:
                                 if p != "#case/parent/" and p != "#case/grandparent/":
                                     self._form_error(
                                         form,
                                         "Unknown prefix remaining: {}".
                                         format(p))
                             if options['save']:
                                 try:
                                     save_xform(form.get_app(), form,
                                                form.source)
                                     app_dirty = True
                                 except:
                                     self._form_error(
                                         form, "Form xml invalid")
                 if app_dirty:
                     app.save()
     logger.info('done with cmitfb_migrate_syntax')
def fix_user_props_caseref(app, module, form, form_ix, dry):
    updated = False
    xform = XForm(form.source)
    refs = {
        xform.resolve_path(ref): vals
        for ref, vals in form.case_references.load.items() if any(
            v.startswith("#user/") for v in vals)
    }
    ref_warnings = []
    for node in xform.model_node.findall("{f}setvalue"):
        if (node.attrib.get('ref') in refs
                and node.attrib.get('event') == "xforms-ready"):
            ref = node.attrib.get('ref')
            ref_values = refs[ref]
            if len(ref_values) != 1:
                ref_warnings.append((ref, " ".join(ref_values)))
                continue
            value = (node.attrib.get('value') or "").replace(" ", "")
            userprop = ref_values[0]
            assert userprop.startswith("#user/"), (ref, userprop)
            prop = userprop[len("#user/"):]
            if value == get_bad_usercase_path(module, form, prop):
                logger.info("%s setvalue %s -> %s", form_ix, userprop, ref)
                node.attrib["value"] = USERPROP_PREFIX + prop
                updated = True
            elif value != (USERPROP_PREFIX + prop).replace(" ", ""):
                ref_warnings.append((ref, "%r (%s)" % (value, userprop)))
    if updated:
        if dry:
            logger.info(
                "updated setvalues in XML:\n%s",
                "\n".join(line for line in ET.tostring(
                    xform.xml, encoding='utf-8').split("\n")
                          if "setvalue" in line))
        else:
            save_xform(app, form, ET.tostring(xform.xml, encoding='utf-8'))
    if ref_warnings:
        for ref, ref_values in ref_warnings:
            logger.warning("%s %s has unexpected #user refs: %s", form_ix, ref,
                           ref_values)
    return updated
Exemple #26
0
 def register_new_user(self, data):
     reg_form = RegisterWebUserForm(data['data'])
     if reg_form.is_valid():
         self._create_new_account(reg_form)
         try:
             requested_domain = request_new_domain(
                 self.request, reg_form, is_new_user=True
             )
             # If user created a form via prelogin demo, create an app for them
             if reg_form.cleaned_data['xform']:
                 lang = 'en'
                 app = Application.new_app(requested_domain, "Untitled Application")
                 module = Module.new_module(_("Untitled Module"), lang)
                 app.add_module(module)
                 save_xform(app, app.new_form(0, "Untitled Form", lang), reg_form.cleaned_data['xform'])
                 app.save()
                 web_user = WebUser.get_by_username(reg_form.cleaned_data['email'])
                 if web_user:
                     update_hubspot_properties(web_user, {
                         'signup_via_demo': 'yes',
                     })
         except NameUnavailableException:
             # technically, the form should never reach this as names are
             # auto-generated now. But, just in case...
             logging.error("There as an issue generating a unique domain name "
                           "for a user during new registration.")
             return {
                 'errors': {
                     'project name unavailable': [],
                 }
             }
         return {
             'success': True,
         }
     logging.error(
         "There was an error processing a new user registration form."
         "This shouldn't happen as validation should be top-notch "
         "client-side. Here is what the errors are: {}".format(reg_form.errors))
     return {
         'errors': reg_form.errors,
     }
Exemple #27
0
    def handle(self, *args, **options):
        if len(args) != 2:
            raise CommandError('Usage: %s\n%s' % (self.args, self.help))
        if options['deploy'] and not options['user']:
            raise CommandError('Deploy argument requires a user')
        elif options['deploy']:
            user = CouchUser.get_by_username(options['user'])
            if not user:
                raise CommandError(
                    "Couldn't find user with username {}".format(
                        options['user']))

        # todo: would be nice if this worked off remote servers too
        path, app_id = args

        app = Application.get(app_id)
        for module_dir in os.listdir(path):
            module_index, name = module_dir.split(' - ')
            module = app.get_module(int(module_index))
            for form_name in os.listdir(os.path.join(path, module_dir)):
                form_index, name = form_name.split(' - ')
                form = module.get_form(int(form_index))
                with open(os.path.join(path, module_dir, form_name)) as f:
                    save_xform(app, form, f.read())

        app.save()
        print 'successfully updated {}'.format(app.name)
        if options['deploy']:
            # make build and star it
            comment = options.get(
                'comment', 'form changes from {0}'.format(
                    datetime.now().strftime("%Y-%m-%d %H:%M")))
            copy = app.make_build(
                comment=comment,
                user_id=user._id,
                previous_version=app.get_latest_app(released_only=False),
            )
            copy.is_released = True
            copy.save(increment_version=False)
            print 'successfully released new version'
    def migrate_app(self, app_id):
        app = Application.get(app_id)
        if app.vellum_case_management:
            logger.info('already migrated app {}'.format(app_id))
            return

        modules = [m for m in app.modules if m.module_type == 'basic']
        for module in modules:
            forms = [f for f in module.forms if f.doc_type == 'Form']
            for form in forms:
                preload = form.actions.case_preload.preload
                if preload:
                    if form.requires == 'case':
                        xform = XForm(form.source)
                        xform.add_case_preloads(preload)
                        save_xform(app, form, ET.tostring(xform.xml))
                        form.case_references = {"load": {path: [case_property]
                            for path, case_property in preload.iteritems()}}
                    form.actions.case_preload = PreloadAction()

        app.vellum_case_management = True
        app.save()
def fix_user_props_caseref(app, module, form, form_ix, dry):
    updated = False
    xform = XForm(form.source)
    refs = {xform.resolve_path(ref): vals
        for ref, vals in six.iteritems(form.case_references.load)
        if any(v.startswith("#user/") for v in vals)}
    ref_warnings = []
    for node in xform.model_node.findall("{f}setvalue"):
        if (node.attrib.get('ref') in refs
                and node.attrib.get('event') == "xforms-ready"):
            ref = node.attrib.get('ref')
            ref_values = refs[ref]
            if len(ref_values) != 1:
                ref_warnings.append((ref, " ".join(ref_values)))
                continue
            value = (node.attrib.get('value') or "").replace(" ", "")
            userprop = ref_values[0]
            assert userprop.startswith("#user/"), (ref, userprop)
            prop = userprop[len("#user/"):]
            if value == get_bad_usercase_path(module, form, prop):
                logger.info("%s setvalue %s -> %s", form_ix, userprop, ref)
                node.attrib["value"] = USERPROP_PREFIX + prop
                updated = True
            elif value != (USERPROP_PREFIX + prop).replace(" ", ""):
                ref_warnings.append((ref, "%r (%s)" % (value, userprop)))
    if updated:
        if dry:
            logger.info("updated setvalues in XML:\n%s", "\n".join(line
                for line in ET.tostring(xform.xml).split("\n")
                if "setvalue" in line))
        else:
            save_xform(app, form, ET.tostring(xform.xml))
    if ref_warnings:
        for ref, ref_values in ref_warnings:
            logger.warning("%s %s has unexpected #user refs: %s",
                form_ix, ref, ref_values)
    return updated
Exemple #30
0
    def update(self, rows):
        try:
            self._check_for_shadow_form_error()
        except BulkAppTranslationsException as e:
            return [(messages.error, six.text_type(e))]

        if not self.itext:
            # This form is empty or malformed. Ignore it.
            return []

        # Setup
        rows = get_unicode_dicts(rows)
        template_translation_el = self._get_template_translation_el()
        self._add_missing_translation_elements_to_itext(template_translation_el)
        self._populate_markdown_stats(rows)
        self.msgs = []

        # Skip labels that have no translation provided
        label_ids_to_skip = self._get_label_ids_to_skip(rows)

        # Update the translations
        for lang in self.langs:
            translation_node = self.itext.find("./{f}translation[@lang='%s']" % lang)
            assert(translation_node.exists())

            for row in rows:
                if row['label'] in label_ids_to_skip:
                    continue
                try:
                    self._add_or_remove_translations(lang, row)
                except BulkAppTranslationsException as e:
                    self.msgs.append((messages.warning, six.text_type(e)))

        save_xform(self.app, self.form, etree.tostring(self.xform.xml))

        return [(t, _('Error in {sheet}: {msg}').format(sheet=self.sheet_name, msg=m)) for (t, m) in self.msgs]
    def handle(self, *args, **options):
        if len(args) != 2:
            raise CommandError("Usage: %s\n%s" % (self.args, self.help))
        if options["deploy"] and not options["user"]:
            raise CommandError("Deploy argument requires a user")
        elif options["deploy"]:
            user = CouchUser.get_by_username(options["user"])
            if not user:
                raise CommandError("Couldn't find user with username {}".format(options["user"]))

        # todo: would be nice if this worked off remote servers too
        path, app_id = args

        app = Application.get(app_id)
        for module_dir in os.listdir(path):
            module_index, name = module_dir.split(" - ")
            module = app.get_module(int(module_index))
            for form_name in os.listdir(os.path.join(path, module_dir)):
                form_index, name = form_name.split(" - ")
                form = module.get_form(int(form_index))
                with open(os.path.join(path, module_dir, form_name)) as f:
                    save_xform(app, form, f.read())

        app.save()
        print "successfully updated {}".format(app.name)
        if options["deploy"]:
            # make build and star it
            comment = options.get(
                "comment", "form changes from {0}".format(datetime.utcnow().strftime(SERVER_DATETIME_FORMAT_NO_SEC))
            )
            copy = app.make_build(
                comment=comment, user_id=user._id, previous_version=app.get_latest_app(released_only=False)
            )
            copy.is_released = True
            copy.save(increment_version=False)
            print "successfully released new version"
Exemple #32
0
def patch_xform(request, domain, app_id, unique_form_id):
    patch = request.POST['patch']
    sha1_checksum = request.POST['sha1']
    case_references = json.loads(request.POST.get('references', "{}"))

    app = get_app(domain, app_id)
    form = app.get_form(unique_form_id)

    current_xml = form.source
    if hashlib.sha1(current_xml.encode('utf-8')).hexdigest() != sha1_checksum:
        return json_response({'status': 'conflict', 'xform': current_xml})

    dmp = diff_match_patch()
    xform, _ = dmp.patch_apply(dmp.patch_fromText(patch), current_xml)
    save_xform(app, form, xform)

    _update_case_refs_from_form_builder(form, case_references)

    response_json = {
        'status': 'ok',
        'sha1': hashlib.sha1(form.source.encode('utf-8')).hexdigest()
    }
    app.save(response_json)
    return json_response(response_json)
def update_form_translations(sheet, rows, missing_cols, app):
    """
    Modify the translations of a form given a sheet of translation data.
    This does not save the changes to the DB.

    :param sheet: a WorksheetJSONReader
    :param rows: The rows of the sheet (we can't get this from the sheet
    because sheet.__iter__ can only be called once)
    :param missing_cols:
    :param app:
    :return:  Returns a list of message tuples. The first item in each tuple is
    a function like django.contrib.messages.error, and the second is a string.
    """
    msgs = []
    mod_text, form_text = sheet.worksheet.title.split("_")
    module_index = int(mod_text.replace("module", "")) - 1
    form_index = int(form_text.replace("form", "")) - 1
    form = app.get_module(module_index).get_form(form_index)
    if isinstance(form, ShadowForm):
        msgs.append((
            messages.warning,
            _("Found a ShadowForm at module-{module_index} form-{form_index} with the name {name}."
              " Cannot translate ShadowForms, skipping.").format(
                  # Add one to revert back to match index in Excel sheet
                  module_index=module_index + 1,
                  form_index=form_index + 1,
                  name=form.default_name(),
              )
        ))
        return msgs

    if form.source:
        xform = form.wrapped_xform()
    else:
        # This Form doesn't have an xform yet. It is empty.
        # Tell the user this?
        return msgs

    try:
        itext = xform.itext_node
    except XFormException:
        return msgs

    # Make language nodes for each language if they don't yet exist
    #
    # Currently operating under the assumption that every xForm has at least
    # one translation element, that each translation element has a text node
    # for each question and that each text node has a value node under it
    template_translation_el = None
    # Get a translation element to be used as a template for new elements
    for lang in app.langs:
        trans_el = itext.find("./{f}translation[@lang='%s']" % lang)
        if trans_el.exists():
            template_translation_el = trans_el
    assert(template_translation_el is not None)
    # Add missing translation elements
    for lang in app.langs:
        trans_el = itext.find("./{f}translation[@lang='%s']" % lang)
        if not trans_el.exists():
            new_trans_el = copy.deepcopy(template_translation_el.xml)
            new_trans_el.set('lang', lang)
            if lang != app.langs[0]:
                # If the language isn't the default language
                new_trans_el.attrib.pop('default', None)
            else:
                new_trans_el.set('default', '')
            itext.xml.append(new_trans_el)

    def _update_translation_node(new_translation, value_node, attributes=None, delete_node=True):
        if delete_node and not new_translation:
            # Remove the node if it already exists
            if value_node.exists():
                value_node.xml.getparent().remove(value_node.xml)
            return

        # Create the node if it does not already exist
        if not value_node.exists():
            e = etree.Element(
                "{f}value".format(**namespaces), attributes
            )
            text_node.xml.append(e)
            value_node = WrappedNode(e)
        # Update the translation
        value_node.xml.tail = ''
        for node in value_node.findall("./*"):
            node.xml.getparent().remove(node.xml)
        escaped_trans = escape_output_value(new_translation)
        value_node.xml.text = escaped_trans.text
        for n in escaped_trans.getchildren():
            value_node.xml.append(n)

    def _looks_like_markdown(str):
        return re.search(r'^\d+[\.\)] |^\*|~~.+~~|# |\*{1,3}\S+\*{1,3}|\[.+\]\(\S+\)', str, re.M)

    def get_markdown_node(text_node_):
        return text_node_.find("./{f}value[@form='markdown']")

    def get_value_node(text_node_):
        try:
            return next(
                n for n in text_node_.findall("./{f}value")
                if 'form' not in n.attrib or n.get('form') == 'default'
            )
        except StopIteration:
            return WrappedNode(None)

    def had_markdown(text_node_):
        """
        Returns True if a Markdown node currently exists for a translation.
        """
        markdown_node_ = get_markdown_node(text_node_)
        return markdown_node_.exists()

    def is_markdown_vetoed(text_node_):
        """
        Return True if the value looks like Markdown but there is no
        Markdown node. It means the user has explicitly told form
        builder that the value isn't Markdown.
        """
        value_node_ = get_value_node(text_node_)
        if not value_node_.exists():
            return False
        old_trans = etree.tostring(value_node_.xml, method="text", encoding="unicode").strip()
        return _looks_like_markdown(old_trans) and not had_markdown(text_node_)

    def has_translation(row_, langs):
        for lang_ in langs:
            for trans_type_ in ['default', 'image', 'audio', 'video']:
                if row_.get(_get_col_key(trans_type_, lang_)):
                    return True

    # Aggregate Markdown vetoes, and translations that currently have Markdown
    vetoes = defaultdict(lambda: False)  # By default, Markdown is not vetoed for a label
    markdowns = defaultdict(lambda: False)  # By default, Markdown is not in use
    for lang in app.langs:
        # If Markdown is vetoed for one language, we apply that veto to other languages too. i.e. If a user has
        # told HQ that "**stars**" in an app's English translation is not Markdown, then we must assume that
        # "**étoiles**" in the French translation is not Markdown either.
        for row in rows:
            label_id = row['label']
            text_node = itext.find("./{f}translation[@lang='%s']/{f}text[@id='%s']" % (lang, label_id))
            vetoes[label_id] = vetoes[label_id] or is_markdown_vetoed(text_node)
            markdowns[label_id] = markdowns[label_id] or had_markdown(text_node)
    # skip labels that have no translation provided
    skip_label = set()
    if form.is_registration_form():
        for row in rows:
            if not has_translation(row, app.langs):
                skip_label.add(row['label'])
        for label in skip_label:
            msgs.append((
                messages.error,
                _("You must provide at least one translation"
                  " for the label '{0}' in sheet '{1}'").format(label, sheet.worksheet.title)
            ))
    # Update the translations
    for lang in app.langs:
        translation_node = itext.find("./{f}translation[@lang='%s']" % lang)
        assert(translation_node.exists())

        for row in rows:
            label_id = row['label']
            if label_id in skip_label:
                continue
            text_node = translation_node.find("./{f}text[@id='%s']" % label_id)
            if not text_node.exists():
                msgs.append((
                    messages.warning,
                    _("Unrecognized translation label {0} in sheet {1}. That row"
                      " has been skipped").format(label_id, sheet.worksheet.title)
                ))
                continue

            translations = dict()
            for trans_type in ['default', 'image', 'audio', 'video']:
                try:
                    col_key = _get_col_key(trans_type, lang)
                    translations[trans_type] = row[col_key]
                except KeyError:
                    # has already been logged as unrecoginzed column
                    continue

            keep_value_node = any(v for k, v in translations.items())

            # Add or remove translations
            for trans_type, new_translation in translations.items():
                if not new_translation and col_key not in missing_cols:
                    # If the cell corresponding to the label for this question
                    # in this language is empty, fall back to another language
                    for l in app.langs:
                        key = _get_col_key(trans_type, l)
                        if key in missing_cols:
                            continue
                        fallback = row[key]
                        if fallback:
                            new_translation = fallback
                            break

                if trans_type == 'default':
                    # plaintext/Markdown
                    if _looks_like_markdown(new_translation) and not vetoes[label_id] or markdowns[label_id]:
                        # If it looks like Markdown, add it ... unless it
                        # looked like Markdown before but it wasn't. If we
                        # have a Markdown node, always keep it. FB 183536
                        _update_translation_node(
                            new_translation,
                            get_markdown_node(text_node),
                            {'form': 'markdown'},
                            # If all translations have been deleted, allow the
                            # Markdown node to be deleted just as we delete
                            # the plaintext node
                            delete_node=(not keep_value_node)
                        )
                    _update_translation_node(
                        new_translation,
                        get_value_node(text_node),
                        {'form': 'default'},
                        delete_node=(not keep_value_node)
                    )
                else:
                    # audio/video/image
                    _update_translation_node(new_translation,
                                             text_node.find("./{f}value[@form='%s']" % trans_type),
                                             {'form': trans_type})

    save_xform(app, form, etree.tostring(xform.xml))
    return msgs
Exemple #34
0
def update_form_translations(sheet, rows, missing_cols, app):
    """
    Modify the translations of a form given a sheet of translation data.
    This does not save the changes to the DB.

    :param sheet: a WorksheetJSONReader
    :param rows: The rows of the sheet (we can't get this from the sheet
    because sheet.__iter__ can only be called once)
    :param missing_cols:
    :param app:
    :return:  Returns a list of message tuples. The first item in each tuple is
    a function like django.contrib.messages.error, and the second is a string.
    """
    msgs = []
    mod_text, form_text = sheet.worksheet.title.split("_")
    module_index = int(mod_text.replace("module", "")) - 1
    form_index = int(form_text.replace("form", "")) - 1
    form = app.get_module(module_index).get_form(form_index)
    if form.source:
        xform = form.wrapped_xform()
    else:
        # This Form doesn't have an xform yet. It is empty.
        # Tell the user this?
        return msgs

    try:
        itext = xform.itext_node
    except XFormException:
        return msgs

    # Make language nodes for each language if they don't yet exist
    #
    # Currently operating under the assumption that every xForm has at least
    # one translation element, that each translation element has a text node
    # for each question and that each text node has a value node under it
    template_translation_el = None
    # Get a translation element to be used as a template for new elements
    for lang in app.langs:
        trans_el = itext.find("./{f}translation[@lang='%s']" % lang)
        if trans_el.exists():
            template_translation_el = trans_el
    assert(template_translation_el is not None)
    # Add missing translation elements
    for lang in app.langs:
        trans_el = itext.find("./{f}translation[@lang='%s']" % lang)
        if not trans_el.exists():
            new_trans_el = copy.deepcopy(template_translation_el.xml)
            new_trans_el.set('lang', lang)
            if lang != app.langs[0]:
                # If the language isn't the default language
                new_trans_el.attrib.pop('default', None)
            else:
                new_trans_el.set('default', '')
            itext.xml.append(new_trans_el)

    def _update_translation_node(new_translation, value_node, attributes=None):
        if new_translation:
            # Create the node if it does not already exist
            if not value_node.exists():
                e = etree.Element(
                    "{f}value".format(**namespaces), attributes
                )
                text_node.xml.append(e)
                value_node = WrappedNode(e)
            # Update the translation
            value_node.xml.tail = ''
            for node in value_node.findall("./*"):
                node.xml.getparent().remove(node.xml)
            escaped_trans = escape_output_value(new_translation)
            value_node.xml.text = escaped_trans.text
            for n in escaped_trans.getchildren():
                value_node.xml.append(n)
        else:
            # Remove the node if it already exists
            if value_node.exists():
                value_node.xml.getparent().remove(value_node.xml)

    def _looks_like_markdown(str):
        return re.search(r'^\d+\. |^\*|~~.+~~|# |\*{1,3}\S+\*{1,3}|\[.+\]\(\S+\)', str)

    # Update the translations
    for lang in app.langs:
        translation_node = itext.find("./{f}translation[@lang='%s']" % lang)
        assert(translation_node.exists())

        for row in rows:
            label_id = row['label']
            text_node = translation_node.find("./{f}text[@id='%s']" % label_id)
            if not text_node.exists():
                msgs.append((
                    messages.warning,
                    "Unrecognized translation label {0} in sheet {1}. That row"
                    " has been skipped". format(label_id, sheet.worksheet.title)
                ))
                continue

            # Add or remove translations
            for trans_type in ['default', 'audio', 'image', 'video']:
                try:
                    col_key = get_col_key(trans_type, lang)
                    new_translation = row[col_key]
                except KeyError:
                    # error has already been logged as unrecoginzed column
                    continue
                if not new_translation and col_key not in missing_cols:
                    # If the cell corresponding to the label for this question
                    # in this language is empty, fall back to another language
                    for l in app.langs:
                        key = get_col_key(trans_type, l)
                        if key in missing_cols:
                            continue
                        fallback = row[key]
                        if fallback:
                            new_translation = fallback
                            break

                if trans_type == 'default':
                    value_node = next(
                        n for n in text_node.findall("./{f}value")
                        if 'form' not in n.attrib
                    )
                    old_translation = etree.tostring(value_node.xml, method="text", encoding="unicode").strip()
                    markdown_node = text_node.find("./{f}value[@form='markdown']")
                    has_markdown = _looks_like_markdown(new_translation)
                    had_markdown = markdown_node.exists()
                    vetoed_markdown = not had_markdown and _looks_like_markdown(old_translation)

                    _update_translation_node(new_translation, value_node)
                    if not((not has_markdown and not had_markdown)    # not dealing with markdown at all
                           or (has_markdown and vetoed_markdown)):    # looks like markdown, but markdown is off
                        _update_translation_node(new_translation if has_markdown and not vetoed_markdown else '',
                                                 markdown_node,
                                                 {'form': 'markdown'})
                else:
                    _update_translation_node(new_translation,
                                             text_node.find("./{f}value[@form='%s']" % trans_type),
                                             {'form': trans_type})

    save_xform(app, form, etree.tostring(xform.xml, encoding="unicode"))
    return msgs
Exemple #35
0
def _ucla_form_modifier(form, question_ids):

    message = ""

    xform = form.wrapped_xform()

    # Get the questions specified in question_ids
    question_dict = {q["value"].split("/")[-1]: FormQuestion.wrap(q) for q in form.get_questions(["en"])}
    question_ids = {q for q in question_ids}.intersection(question_dict.keys())
    questions = [question_dict[k] for k in question_ids]

    # Get the existing subcases
    existing_subcases = {c.case_name:c for c in form.actions.subcases}

    message += "Found %s questions.\n" % len(questions)

    for question in questions:
        for option in question.options:

            hidden_value_tag = question.value.split("/")[-1] + "-" + option.value
            hidden_value_path = "/data/" + hidden_value_tag
            hidden_value_text = option.label

            # Create new hidden values for each question option if they don't already exist:

            if hidden_value_tag not in question_dict:

                # Add data element
                tag = "{x}%s" % hidden_value_tag
                element = etree.Element(tag.format(**namespaces))
                xform.data_node.append(element)

                # Add bind
                xform.itext_node.addprevious(_make_elem("bind",{
                    "nodeset": xform.resolve_path(hidden_value_path),
                    "calculate": '"'+hidden_value_text+'"'
                }))

                message += "Node " + hidden_value_path + " created!\n"
            else:
                message += "Node " + hidden_value_path + " already exists, skipping.\n"

            # Create FormActions for opening subcases

            if hidden_value_path not in existing_subcases:
                action = OpenSubCaseAction(
                    condition=FormActionCondition(
                        type='if',
                        question=question.value,
                        operator='selected',
                        answer=option.value,
                    ),
                    case_name=hidden_value_path,
                    case_type='task',
                    # Note, the case properties will not necessarily be created in the order given.
                    case_properties={
                        'task_responsible': '/data/task_responsible',
                        'task_due': '/data/task_due',
                        'owner_id': '/data/owner_id',
                        'task_risk_factor': '/data/task_risk_factor',
                        'study_id': '/data/study_id',
                        'patient_name': '/data/patient_name'
                    },
                    close_condition=FormActionCondition(
                        answer=None,
                        operator=None,
                        question=None,
                        type='never'
                    )
                )
                form.actions.subcases.append(action)
                message += "OpenSubCaseAction " + hidden_value_path + " created!\n"
            else:
                message += "OpenSubCaseAction " + hidden_value_path + " already exists, skipping.\n"

    app = form.get_app()
    # Save the xform modifications
    save_xform(app, form, etree.tostring(xform.xml, encoding="unicode"))
    # save the action modifications
    app.save()
    message += "Form saved.\n"
    return message
Exemple #36
0
 def test_save_xform_changes_generic_xmlns(self):
     source = self.get_source(xmlns=GENERIC_XMLNS)
     self.assertNotIn(self.form.xmlns, source)
     save_xform(self.app, self.form, source.encode('utf-8'))
     self.assertIn(self.form.xmlns, self.form.source)
     self.assertNotEqual(self.form.xmlns, GENERIC_XMLNS)
Exemple #37
0
def _edit_form_attr(request, domain, app_id, unique_form_id, attr):
    """
    Called to edit any (supported) form attribute, given by attr

    """

    ajax = json.loads(request.POST.get('ajax', 'true'))
    resp = {}

    app = get_app(domain, app_id)
    try:
        form = app.get_form(unique_form_id)
    except FormNotFoundException as e:
        if ajax:
            return HttpResponseBadRequest(unicode(e))
        else:
            messages.error(request, _("There was an error saving, please try again!"))
            return back_to_main(request, domain, app_id=app_id)
    lang = request.COOKIES.get('lang', app.langs[0])

    def should_edit(attribute):
        return attribute in request.POST

    if should_edit("name"):
        name = request.POST['name']
        form.name[lang] = name
        xform = form.wrapped_xform()
        if xform.exists():
            xform.set_name(name)
            save_xform(app, form, xform.render())
        resp['update'] = {'.variable-form_name': trans(form.name, [lang], use_delim=False)}
    if should_edit('comment'):
        form.comment = request.POST['comment']
    if should_edit("xform") or "xform" in request.FILES:
        try:
            # support FILES for upload and POST for ajax post from Vellum
            try:
                xform = request.FILES.get('xform').read()
            except Exception:
                xform = request.POST.get('xform')
            else:
                try:
                    xform = unicode(xform, encoding="utf-8")
                except Exception:
                    raise Exception("Error uploading form: Please make sure your form is encoded in UTF-8")
            if request.POST.get('cleanup', False):
                try:
                    # First, we strip all newlines and reformat the DOM.
                    px = parseString(xform.replace('\r\n', '')).toprettyxml()
                    # Then we remove excess newlines from the DOM output.
                    text_re = re.compile('>\n\s+([^<>\s].*?)\n\s+</', re.DOTALL)
                    prettyXml = text_re.sub('>\g<1></', px)
                    xform = prettyXml
                except Exception:
                    pass
            if xform:
                save_xform(app, form, xform)
            else:
                raise Exception("You didn't select a form to upload")
        except Exception, e:
            if ajax:
                return HttpResponseBadRequest(unicode(e))
            else:
                messages.error(request, unicode(e))
Exemple #38
0
 def test_save_xform_changes_empty_xmlns(self):
     source = self.get_source(xmlns=None)
     self.assertNotIn(self.form.xmlns, source)
     save_xform(self.app, self.form, source.encode('utf-8'))
     self.assertIn(self.form.xmlns, self.form.source)
     self.assertNotEqual(self.form.xmlns, GENERIC_XMLNS)
Exemple #39
0
 def test_save_xform_changes_generic_xmlns(self):
     source = self.get_source(xmlns=GENERIC_XMLNS)
     self.assertNotIn(self.form.xmlns, source)
     save_xform(self.app, self.form, source)
     self.assertIn(self.form.xmlns, self.form.source)
     self.assertNotEqual(self.form.xmlns, GENERIC_XMLNS)
Exemple #40
0
def update_form_translations(sheet, rows, missing_cols, app):
    """
    Modify the translations of a form given a sheet of translation data.
    This does not save the changes to the DB.

    :param sheet: a WorksheetJSONReader
    :param rows: The rows of the sheet (we can't get this from the sheet
    because sheet.__iter__ can only be called once)
    :param missing_cols:
    :param app:
    :return:  Returns a list of message tuples. The first item in each tuple is
    a function like django.contrib.messages.error, and the second is a string.
    """
    msgs = []
    mod_text, form_text = sheet.worksheet.title.split("_")
    module_index = int(mod_text.replace("module", "")) - 1
    form_index = int(form_text.replace("form", "")) - 1
    form = app.get_module(module_index).get_form(form_index)
    if form.source:
        xform = form.wrapped_xform()
    else:
        # This Form doesn't have an xform yet. It is empty.
        # Tell the user this?
        return msgs

    try:
        itext = xform.itext_node
    except XFormException:
        return msgs

    # Make language nodes for each language if they don't yet exist
    #
    # Currently operating under the assumption that every xForm has at least
    # one translation element, that each translation element has a text node
    # for each question and that each text node has a value node under it
    template_translation_el = None
    # Get a translation element to be used as a template for new elements
    for lang in app.langs:
        trans_el = itext.find("./{f}translation[@lang='%s']" % lang)
        if trans_el.exists():
            template_translation_el = trans_el
    assert(template_translation_el is not None)
    # Add missing translation elements
    for lang in app.langs:
        trans_el = itext.find("./{f}translation[@lang='%s']" % lang)
        if not trans_el.exists():
            new_trans_el = copy.deepcopy(template_translation_el.xml)
            new_trans_el.set('lang', lang)
            if lang != app.langs[0]:
                # If the language isn't the default language
                new_trans_el.attrib.pop('default', None)
            else:
                new_trans_el.set('default', '')
            itext.xml.append(new_trans_el)

    def _update_translation_node(new_translation, value_node, attributes=None, delete_node=True):
        if delete_node and not new_translation:
            # Remove the node if it already exists
            if value_node.exists():
                value_node.xml.getparent().remove(value_node.xml)
            return

        # Create the node if it does not already exist
        if not value_node.exists():
            e = etree.Element(
                "{f}value".format(**namespaces), attributes
            )
            text_node.xml.append(e)
            value_node = WrappedNode(e)
        # Update the translation
        value_node.xml.tail = ''
        for node in value_node.findall("./*"):
            node.xml.getparent().remove(node.xml)
        escaped_trans = escape_output_value(new_translation)
        value_node.xml.text = escaped_trans.text
        for n in escaped_trans.getchildren():
            value_node.xml.append(n)

    def _looks_like_markdown(str):
        return re.search(r'^\d+[\.\)] |^\*|~~.+~~|# |\*{1,3}\S+\*{1,3}|\[.+\]\(\S+\)', str, re.M)

    def get_markdown_node(text_node_):
        return text_node_.find("./{f}value[@form='markdown']")

    def get_value_node(text_node_):
        try:
            return next(
                n for n in text_node_.findall("./{f}value")
                if 'form' not in n.attrib or n.get('form') == 'default'
            )
        except StopIteration:
            return WrappedNode(None)

    def had_markdown(text_node_):
        """
        Returns True if a Markdown node currently exists for a translation.
        """
        markdown_node_ = get_markdown_node(text_node_)
        return markdown_node_.exists()

    def is_markdown_vetoed(text_node_):
        """
        Return True if the value looks like Markdown but there is no
        Markdown node. It means the user has explicitly told form
        builder that the value isn't Markdown.
        """
        value_node_ = get_value_node(text_node_)
        if not value_node_.exists():
            return False
        old_trans = etree.tostring(value_node_.xml, method="text", encoding="unicode").strip()
        return _looks_like_markdown(old_trans) and not had_markdown(text_node_)

    # Aggregate Markdown vetoes, and translations that currently have Markdown
    vetoes = defaultdict(lambda: False)  # By default, Markdown is not vetoed for a label
    markdowns = defaultdict(lambda: False)  # By default, Markdown is not in use
    for lang in app.langs:
        # If Markdown is vetoed for one language, we apply that veto to other languages too. i.e. If a user has
        # told HQ that "**stars**" in an app's English translation is not Markdown, then we must assume that
        # "**étoiles**" in the French translation is not Markdown either.
        for row in rows:
            label_id = row['label']
            text_node = itext.find("./{f}translation[@lang='%s']/{f}text[@id='%s']" % (lang, label_id))
            vetoes[label_id] = vetoes[label_id] or is_markdown_vetoed(text_node)
            markdowns[label_id] = markdowns[label_id] or had_markdown(text_node)

    # Update the translations
    for lang in app.langs:
        translation_node = itext.find("./{f}translation[@lang='%s']" % lang)
        assert(translation_node.exists())

        for row in rows:
            label_id = row['label']
            text_node = translation_node.find("./{f}text[@id='%s']" % label_id)
            if not text_node.exists():
                msgs.append((
                    messages.warning,
                    "Unrecognized translation label {0} in sheet {1}. That row"
                    " has been skipped". format(label_id, sheet.worksheet.title)
                ))
                continue

            translations = dict()
            for trans_type in ['default', 'audio', 'image', 'video']:
                try:
                    col_key = get_col_key(trans_type, lang)
                    translations[trans_type] = row[col_key]
                except KeyError:
                    # has already been logged as unrecoginzed column
                    continue

            keep_value_node = any(v for k, v in translations.items())

            # Add or remove translations
            for trans_type, new_translation in translations.items():
                if not new_translation and col_key not in missing_cols:
                    # If the cell corresponding to the label for this question
                    # in this language is empty, fall back to another language
                    for l in app.langs:
                        key = get_col_key(trans_type, l)
                        if key in missing_cols:
                            continue
                        fallback = row[key]
                        if fallback:
                            new_translation = fallback
                            break

                if trans_type == 'default':
                    # plaintext/Markdown
                    if _looks_like_markdown(new_translation) and not vetoes[label_id] or markdowns[label_id]:
                        # If it looks like Markdown, add it ... unless it
                        # looked like Markdown before but it wasn't. If we
                        # have a Markdown node, always keep it. FB 183536
                        _update_translation_node(
                            new_translation,
                            get_markdown_node(text_node),
                            {'form': 'markdown'},
                            # If all translations have been deleted, allow the
                            # Markdown node to be deleted just as we delete
                            # the plaintext node
                            delete_node=(not keep_value_node)
                        )
                    _update_translation_node(
                        new_translation,
                        get_value_node(text_node),
                        {'form': 'default'},
                        delete_node=(not keep_value_node)
                    )
                else:
                    # audio/video/image
                    _update_translation_node(new_translation,
                                             text_node.find("./{f}value[@form='%s']" % trans_type),
                                             {'form': trans_type})

    save_xform(app, form, etree.tostring(xform.xml, encoding="unicode"))
    return msgs
Exemple #41
0
def update_form_translations(sheet, rows, missing_cols, app):
    """
    Modify the translations of a form given a sheet of translation data.
    This does not save the changes to the DB.

    :param sheet: a WorksheetJSONReader
    :param rows: The rows of the sheet (we can't get this from the sheet
    because sheet.__iter__ can only be called once)
    :param missing_cols:
    :param app:
    :return:  Returns a list of message tuples. The first item in each tuple is
    a function like django.contrib.messages.error, and the second is a string.
    """
    msgs = []
    mod_text, form_text = sheet.worksheet.title.split("_")
    module_index = int(mod_text.replace("module", "")) - 1
    form_index = int(form_text.replace("form", "")) - 1
    form = app.get_module(module_index).get_form(form_index)
    if form.source:
        xform = form.wrapped_xform()
    else:
        # This Form doesn't have an xform yet. It is empty.
        # Tell the user this?
        return msgs

    try:
        itext = xform.itext_node
    except XFormException:
        return msgs

    # Make language nodes for each language if they don't yet exist
    #
    # Currently operating under the assumption that every xForm has at least
    # one translation element, that each translation element has a text node
    # for each question and that each text node has a value node under it
    template_translation_el = None
    # Get a translation element to be used as a template for new elements
    for lang in app.langs:
        trans_el = itext.find("./{f}translation[@lang='%s']" % lang)
        if trans_el.exists():
            template_translation_el = trans_el
    assert template_translation_el is not None
    # Add missing translation elements
    for lang in app.langs:
        trans_el = itext.find("./{f}translation[@lang='%s']" % lang)
        if not trans_el.exists():
            new_trans_el = copy.deepcopy(template_translation_el.xml)
            new_trans_el.set("lang", lang)
            if lang != app.langs[0]:
                # If the language isn't the default language
                new_trans_el.attrib.pop("default", None)
            else:
                new_trans_el.set("default", "")
            itext.xml.append(new_trans_el)

    # Update the translations
    for lang in app.langs:
        translation_node = itext.find("./{f}translation[@lang='%s']" % lang)
        assert translation_node.exists()

        for row in rows:
            label_id = row["label"]
            text_node = translation_node.find("./{f}text[@id='%s']" % label_id)
            if not text_node.exists():
                msgs.append(
                    (
                        messages.warning,
                        "Unrecognized translation label {0} in sheet {1}. That row"
                        " has been skipped".format(label_id, sheet.worksheet.title),
                    )
                )
                continue

            # Add or remove translations
            for trans_type in ["default", "audio", "image", "video"]:

                if trans_type == "default":
                    attributes = None
                    value_node = next(n for n in text_node.findall("./{f}value") if "form" not in n.attrib)
                else:
                    attributes = {"form": trans_type}
                    value_node = text_node.find("./{f}value[@form='%s']" % trans_type)

                col_key = get_col_key(trans_type, lang)
                new_translation = row[col_key]
                if not new_translation and col_key not in missing_cols:
                    # If the cell corresponding to the label for this question
                    # in this language is empty, fall back to another language
                    for l in app.langs:
                        fallback = row[get_col_key(trans_type, l)]
                        if fallback:
                            new_translation = fallback
                            break

                if new_translation:
                    # Create the node if it does not already exist
                    if not value_node.exists():
                        e = etree.Element("{f}value".format(**namespaces), attributes)
                        text_node.xml.append(e)
                        value_node = WrappedNode(e)
                    # Update the translation
                    value_node.xml.text = new_translation
                else:
                    # Remove the node if it already exists
                    if value_node.exists():
                        value_node.xml.getparent().remove(value_node.xml)

    save_xform(app, form, etree.tostring(xform.xml, encoding="unicode"))
    return msgs
Exemple #42
0
def _edit_form_attr(request, domain, app_id, form_unique_id, attr):
    """
    Called to edit any (supported) form attribute, given by attr

    """

    ajax = json.loads(request.POST.get('ajax', 'true'))
    resp = {}

    app = get_app(domain, app_id)
    try:
        form = app.get_form(form_unique_id)
    except FormNotFoundException as e:
        if ajax:
            return HttpResponseBadRequest(str(e))
        else:
            messages.error(request, _("There was an error saving, please try again!"))
            return back_to_main(request, domain, app_id=app_id)
    lang = request.COOKIES.get('lang', app.langs[0])

    def should_edit(attribute):
        return attribute in request.POST

    if 'sha1' in request.POST and (should_edit("xform") or "xform" in request.FILES):
        conflict = _get_xform_conflict_response(form, request.POST['sha1'])
        if conflict is not None:
            return conflict

    if should_edit("name"):
        name = request.POST['name']
        form.name[lang] = name
        if not form.form_type == "shadow_form":
            xform = form.wrapped_xform()
            if xform.exists():
                xform.set_name(name)
                save_xform(app, form, xform.render())
        resp['update'] = {'.variable-form_name': clean_trans(form.name, [lang])}

    if should_edit('comment'):
        form.comment = request.POST['comment']

    if should_edit("xform") or "xform" in request.FILES:
        try:
            # support FILES for upload and POST for ajax post from Vellum
            try:
                xform = request.FILES.get('xform').read()
            except Exception:
                xform = request.POST.get('xform')
            else:
                try:
                    xform = str(xform, encoding="utf-8")
                except Exception:
                    raise Exception("Error uploading form: Please make sure your form is encoded in UTF-8")

            if request.POST.get('cleanup', False):
                try:
                    # First, we strip all newlines and reformat the DOM.
                    px = parseString(xform.replace('\r\n', '')).toprettyxml()
                    # Then we remove excess newlines from the DOM output.
                    text_re = re.compile(r'>\n\s+([^<>\s].*?)\n\s+</', re.DOTALL)
                    prettyXml = text_re.sub(r'>\g<1></', px)
                    xform = prettyXml
                except Exception:
                    pass
            if xform:
                if isinstance(xform, str):
                    xform = xform.encode('utf-8')
                save_xform(app, form, xform)
            else:
                raise Exception("You didn't select a form to upload")
        except Exception as e:
            notify_exception(request, str(e))
            if ajax:
                return HttpResponseBadRequest(str(e))
            else:
                messages.error(request, str(e))
    if should_edit("references") or should_edit("case_references"):
        form.case_references = _get_case_references(request.POST)
    if should_edit("show_count"):
        show_count = request.POST['show_count']
        form.show_count = True if show_count == "True" else False
    if should_edit("put_in_root"):
        put_in_root = request.POST['put_in_root']
        form.put_in_root = True if put_in_root == "True" else False
    if should_edit('form_filter'):
        form.form_filter = request.POST['form_filter']
    if should_edit('post_form_workflow'):
        form.post_form_workflow = request.POST['post_form_workflow']
    if should_edit('auto_gps_capture'):
        form.auto_gps_capture = request.POST['auto_gps_capture'] == 'true'
    if should_edit('is_release_notes_form'):
        form.is_release_notes_form = request.POST['is_release_notes_form'] == 'true'
    if should_edit('enable_release_notes'):
        form.enable_release_notes = request.POST['enable_release_notes'] == 'true'
        if not form.is_release_notes_form and form.enable_release_notes:
            return json_response(
                {'message': _("You can't enable a form as release notes without allowing it as "
                    "a release notes form <TODO messaging>")},
                status_code=400
            )
    if (should_edit("form_links_xpath_expressions")
            and should_edit("form_links_form_ids")
            and toggles.FORM_LINK_WORKFLOW.enabled(domain)):
        form_links = zip(
            request.POST.getlist('form_links_xpath_expressions'),
            request.POST.getlist('form_links_form_ids'),
            [
                json.loads(datum_json) if datum_json else []
                for datum_json in request.POST.getlist('datums_json')
            ],
        )
        module_unique_ids = [m.unique_id for m in app.get_modules()]
        form.form_links = [FormLink(
            xpath=link[0],
            form_id=link[1] if link[1] not in module_unique_ids else None,
            module_unique_id=link[1] if link[1] in module_unique_ids else None,
            datums=[
                FormDatum(name=datum['name'], xpath=datum['xpath'])
                for datum in link[2]
            ]
        ) for link in form_links]

    if should_edit('post_form_workflow_fallback'):
        form.post_form_workflow_fallback = request.POST.get('post_form_workflow_fallback')

    if should_edit('custom_instances'):
        instances = json.loads(request.POST.get('custom_instances'))
        try:  # validate that custom instances can be added into the XML
            for instance in instances:
                etree.fromstring(
                    "<instance id='{}' src='{}' />".format(
                        instance.get('instanceId'),
                        instance.get('instancePath')
                    )
                )
        except etree.XMLSyntaxError as error:
            return json_response(
                {'message': _("There was an issue with your custom instances: {}").format(error)},
                status_code=400
            )

        form.custom_instances = [
            CustomInstance(
                instance_id=instance.get("instanceId"),
                instance_path=instance.get("instancePath"),
            ) for instance in instances
        ]

    if should_edit('custom_assertions'):
        assertions = json.loads(request.POST.get('custom_assertions'))
        try:  # validate that custom assertions can be added into the XML
            for assertion in assertions:
                etree.fromstring(
                    '<assertion test="{test}"><text><locale id="abc.def"/>{text}</text></assertion>'.format(
                        **assertion
                    )
                )
        except etree.XMLSyntaxError as error:
            return json_response(
                {'message': _("There was an issue with your custom assertions: {}").format(error)},
                status_code=400
            )

        existing_assertions = {assertion.test: assertion for assertion in form.custom_assertions}
        new_assertions = []
        for assertion in assertions:
            try:
                new_assertion = existing_assertions[assertion.get('test')]
                new_assertion.text[lang] = assertion.get('text')
            except KeyError:
                new_assertion = CustomAssertion(
                    test=assertion.get('test'),
                    text={lang: assertion.get('text')}
                )
            new_assertions.append(new_assertion)

        form.custom_assertions = new_assertions

    if should_edit("shadow_parent"):
        form.shadow_parent_form_id = request.POST['shadow_parent']

    if should_edit("custom_icon_form"):
        error_message = handle_custom_icon_edits(request, form, lang)
        if error_message:
            return json_response(
                {'message': error_message},
                status_code=400
            )

    if should_edit('session_endpoint_id'):
        raw_endpoint_id = request.POST['session_endpoint_id']
        try:
            set_session_endpoint(form, raw_endpoint_id, app)
        except InvalidSessionEndpoint as e:
            return json_response({'message': str(e)}, status_code=400)

    if should_edit('function_datum_endpoints'):
        if request.POST['function_datum_endpoints']:
            form.function_datum_endpoints = request.POST['function_datum_endpoints'].replace(" ", "").split(",")
        else:
            form.function_datum_endpoints = []

    handle_media_edits(request, form, should_edit, resp, lang)

    app.save(resp)
    notify_form_changed(domain, request.couch_user, app_id, form_unique_id)
    if ajax:
        return HttpResponse(json.dumps(resp))
    else:
        return back_to_main(request, domain, app_id=app_id, form_unique_id=form_unique_id)
Exemple #43
0
def _edit_form_attr(request, domain, app_id, unique_form_id, attr):
    """
    Called to edit any (supported) form attribute, given by attr

    """

    ajax = json.loads(request.POST.get('ajax', 'true'))
    resp = {}

    app = get_app(domain, app_id)
    try:
        form = app.get_form(unique_form_id)
    except FormNotFoundException as e:
        if ajax:
            return HttpResponseBadRequest(unicode(e))
        else:
            messages.error(request, _("There was an error saving, please try again!"))
            return back_to_main(request, domain, app_id=app_id)
    lang = request.COOKIES.get('lang', app.langs[0])

    def should_edit(attribute):
        return attribute in request.POST

    if should_edit("name"):
        name = request.POST['name']
        form.name[lang] = name
        xform = form.wrapped_xform()
        if xform.exists():
            xform.set_name(name)
            save_xform(app, form, xform.render())
        resp['update'] = {'.variable-form_name': trans(form.name, [lang], use_delim=False)}
    if should_edit('comment'):
        form.comment = request.POST['comment']
    if should_edit("xform") or "xform" in request.FILES:
        try:
            # support FILES for upload and POST for ajax post from Vellum
            try:
                xform = request.FILES.get('xform').read()
            except Exception:
                xform = request.POST.get('xform')
            else:
                try:
                    xform = unicode(xform, encoding="utf-8")
                except Exception:
                    raise Exception("Error uploading form: Please make sure your form is encoded in UTF-8")
            if request.POST.get('cleanup', False):
                try:
                    # First, we strip all newlines and reformat the DOM.
                    px = parseString(xform.replace('\r\n', '')).toprettyxml()
                    # Then we remove excess newlines from the DOM output.
                    text_re = re.compile('>\n\s+([^<>\s].*?)\n\s+</', re.DOTALL)
                    prettyXml = text_re.sub('>\g<1></', px)
                    xform = prettyXml
                except Exception:
                    pass
            if xform:
                save_xform(app, form, xform)
            else:
                raise Exception("You didn't select a form to upload")
        except Exception, e:
            if ajax:
                return HttpResponseBadRequest(unicode(e))
            else:
                messages.error(request, unicode(e))
Exemple #44
0
def edit_form_attr(request, domain, app_id, unique_form_id, attr):
    """
    Called to edit any (supported) form attribute, given by attr

    """

    app = get_app(domain, app_id)
    form = app.get_form(unique_form_id)
    lang = request.COOKIES.get('lang', app.langs[0])
    ajax = json.loads(request.POST.get('ajax', 'true'))

    resp = {}

    def should_edit(attribute):
        if attribute in request.POST:
            return True
        elif attribute in request.FILES:
            return True
        else:
            return False

    if should_edit("user_reg_data"):
        # should be user_registrations only
        data = json.loads(request.POST['user_reg_data'])
        data_paths = data['data_paths']
        data_paths_dict = {}
        for path in data_paths:
            data_paths_dict[path.split('/')[-1]] = path
        form.data_paths = data_paths_dict

    if should_edit("name"):
        name = request.POST['name']
        form.name[lang] = name
        xform = form.wrapped_xform()
        if xform.exists():
            xform.set_name(name)
            save_xform(app, form, xform.render())
        resp['update'] = {'.variable-form_name': form.name[lang]}
    if should_edit('comment'):
        form.comment = request.POST['comment']
    if should_edit("xform"):
        try:
            # support FILES for upload and POST for ajax post from Vellum
            try:
                xform = request.FILES.get('xform').read()
            except Exception:
                xform = request.POST.get('xform')
            else:
                try:
                    xform = unicode(xform, encoding="utf-8")
                except Exception:
                    raise Exception("Error uploading form: Please make sure your form is encoded in UTF-8")
            if request.POST.get('cleanup', False):
                try:
                    # First, we strip all newlines and reformat the DOM.
                    px = parseString(xform.replace('\r\n', '')).toprettyxml()
                    # Then we remove excess newlines from the DOM output.
                    text_re = re.compile('>\n\s+([^<>\s].*?)\n\s+</', re.DOTALL)
                    prettyXml = text_re.sub('>\g<1></', px)
                    xform = prettyXml
                except Exception:
                    pass
            if xform:
                save_xform(app, form, xform)
            else:
                raise Exception("You didn't select a form to upload")
        except Exception, e:
            if ajax:
                return HttpResponseBadRequest(unicode(e))
            else:
                messages.error(request, unicode(e))
Exemple #45
0
def _ucla_form_modifier(form, question_ids):

    message = ""

    xform = form.wrapped_xform()

    # Get the questions specified in question_ids
    question_dict = {
        q["value"].split("/")[-1]: FormQuestion.wrap(q)
        for q in form.get_questions(["en"])
    }
    question_ids = {q for q in question_ids}.intersection(question_dict.keys())
    questions = [question_dict[k] for k in question_ids]

    # Get the existing subcases
    existing_subcases = {c.case_name: c for c in form.actions.subcases}

    message += "Found %s questions.\n" % len(questions)

    for question in questions:
        for option in question.options:

            hidden_value_tag = question.value.split(
                "/")[-1] + "-" + option.value
            hidden_value_path = "/data/" + hidden_value_tag
            hidden_value_text = option.label

            # Create new hidden values for each question option if they don't already exist:

            if hidden_value_tag not in question_dict:

                # Add data element
                tag = "{x}%s" % hidden_value_tag
                element = etree.Element(tag.format(**namespaces))
                xform.data_node.append(element)

                # Add bind
                xform.itext_node.addprevious(
                    _make_elem(
                        "bind", {
                            "nodeset": xform.resolve_path(hidden_value_path),
                            "calculate": '"' + hidden_value_text + '"'
                        }))

                message += "Node " + hidden_value_path + " created!\n"
            else:
                message += "Node " + hidden_value_path + " already exists, skipping.\n"

            # Create FormActions for opening subcases

            if hidden_value_path not in existing_subcases:
                action = OpenSubCaseAction(
                    condition=FormActionCondition(
                        type='if',
                        question=question.value,
                        operator='selected',
                        answer=option.value,
                    ),
                    case_name=hidden_value_path,
                    case_type='task',
                    # Note, the case properties will not necessarily be created in the order given.
                    case_properties={
                        'task_responsible': '/data/task_responsible',
                        'task_due': '/data/task_due',
                        'owner_id': '/data/owner_id',
                        'task_risk_factor': '/data/task_risk_factor',
                        'study_id': '/data/study_id',
                        'patient_name': '/data/patient_name'
                    },
                    close_condition=FormActionCondition(answer=None,
                                                        operator=None,
                                                        question=None,
                                                        type='never'))
                form.actions.subcases.append(action)
                message += "OpenSubCaseAction " + hidden_value_path + " created!\n"
            else:
                message += "OpenSubCaseAction " + hidden_value_path + " already exists, skipping.\n"

    app = form.get_app()
    # Save the xform modifications
    save_xform(app, form, etree.tostring(xform.xml, encoding="unicode"))
    # save the action modifications
    app.save()
    message += "Form saved.\n"
    return message
Exemple #46
0
def _edit_form_attr(request, domain, app_id, form_unique_id, attr):
    """
    Called to edit any (supported) form attribute, given by attr

    """

    ajax = json.loads(request.POST.get('ajax', 'true'))
    resp = {}

    app = get_app(domain, app_id)
    try:
        form = app.get_form(form_unique_id)
    except FormNotFoundException as e:
        if ajax:
            return HttpResponseBadRequest(unicode(e))
        else:
            messages.error(request,
                           _("There was an error saving, please try again!"))
            return back_to_main(request, domain, app_id=app_id)
    lang = request.COOKIES.get('lang', app.langs[0])

    def should_edit(attribute):
        return attribute in request.POST

    if should_edit("name"):
        name = request.POST['name']
        form.name[lang] = name
        if not form.form_type == "shadow_form":
            xform = form.wrapped_xform()
            if xform.exists():
                xform.set_name(name)
                save_xform(app, form, xform.render())
        resp['update'] = {
            '.variable-form_name': trans(form.name, [lang], use_delim=False)
        }
    if should_edit('comment'):
        form.comment = request.POST['comment']
    if should_edit("xform") or "xform" in request.FILES:
        try:
            # support FILES for upload and POST for ajax post from Vellum
            try:
                xform = request.FILES.get('xform').read()
            except Exception:
                xform = request.POST.get('xform')
            else:
                try:
                    xform = unicode(xform, encoding="utf-8")
                except Exception:
                    raise Exception(
                        "Error uploading form: Please make sure your form is encoded in UTF-8"
                    )
            if request.POST.get('cleanup', False):
                try:
                    # First, we strip all newlines and reformat the DOM.
                    px = parseString(xform.replace('\r\n', '')).toprettyxml()
                    # Then we remove excess newlines from the DOM output.
                    text_re = re.compile('>\n\s+([^<>\s].*?)\n\s+</',
                                         re.DOTALL)
                    prettyXml = text_re.sub('>\g<1></', px)
                    xform = prettyXml
                except Exception:
                    pass
            if xform:
                save_xform(app, form, xform)
            else:
                raise Exception("You didn't select a form to upload")
        except Exception as e:
            if ajax:
                return HttpResponseBadRequest(unicode(e))
            else:
                messages.error(request, unicode(e))
    if should_edit("references") or should_edit("case_references"):
        form.case_references = _get_case_references(request.POST)
    if should_edit("show_count"):
        show_count = request.POST['show_count']
        form.show_count = True if show_count == "True" else False
    if should_edit("put_in_root"):
        put_in_root = request.POST['put_in_root']
        form.put_in_root = True if put_in_root == "True" else False
    if should_edit('form_filter'):
        form.form_filter = request.POST['form_filter']
    if should_edit('post_form_workflow'):
        form.post_form_workflow = request.POST['post_form_workflow']
    if should_edit('auto_gps_capture'):
        form.auto_gps_capture = request.POST['auto_gps_capture'] == 'true'
    if should_edit('no_vellum'):
        form.no_vellum = request.POST['no_vellum'] == 'true'
    if (should_edit("form_links_xpath_expressions")
            and should_edit("form_links_form_ids")
            and toggles.FORM_LINK_WORKFLOW.enabled(domain)):
        form_links = zip(
            request.POST.getlist('form_links_xpath_expressions'),
            request.POST.getlist('form_links_form_ids'),
            [
                json.loads(datum_json) if datum_json else []
                for datum_json in request.POST.getlist('datums_json')
            ],
        )
        form.form_links = [
            FormLink(xpath=link[0],
                     form_id=link[1],
                     datums=[
                         FormDatum(name=datum['name'], xpath=datum['xpath'])
                         for datum in link[2]
                     ]) for link in form_links
        ]

    if should_edit('post_form_workflow_fallback'):
        form.post_form_workflow_fallback = request.POST.get(
            'post_form_workflow_fallback')

    if should_edit('custom_instances'):
        instances = json.loads(request.POST.get('custom_instances'))
        try:  # validate that custom instances can be added into the XML
            for instance in instances:
                etree.fromstring("<instance id='{}' src='{}' />".format(
                    instance.get('instanceId'), instance.get('instancePath')))
        except etree.XMLSyntaxError as error:
            return json_response(
                {
                    'message':
                    _("There was an issue with your custom instances: {}").
                    format(error.message)
                },
                status_code=400)

        form.custom_instances = [
            CustomInstance(
                instance_id=instance.get("instanceId"),
                instance_path=instance.get("instancePath"),
            ) for instance in instances
        ]
    if should_edit("shadow_parent"):
        form.shadow_parent_form_id = request.POST['shadow_parent']

    if should_edit("custom_icon_form"):
        error_message = handle_custom_icon_edits(request, form, lang)
        if error_message:
            return json_response({'message': error_message}, status_code=400)
    handle_media_edits(request, form, should_edit, resp, lang)

    app.save(resp)
    notify_form_changed(domain, request.couch_user, app_id, form_unique_id)
    if ajax:
        return HttpResponse(json.dumps(resp))
    else:
        return back_to_main(request,
                            domain,
                            app_id=app_id,
                            form_unique_id=form_unique_id)
Exemple #47
0
def _edit_form_attr(request, domain, app_id, form_unique_id, attr):
    """
    Called to edit any (supported) form attribute, given by attr

    """

    ajax = json.loads(request.POST.get('ajax', 'true'))
    resp = {}

    app = get_app(domain, app_id)
    try:
        form = app.get_form(form_unique_id)
    except FormNotFoundException as e:
        if ajax:
            return HttpResponseBadRequest(six.text_type(e))
        else:
            messages.error(request, _("There was an error saving, please try again!"))
            return back_to_main(request, domain, app_id=app_id)
    lang = request.COOKIES.get('lang', app.langs[0])

    def should_edit(attribute):
        return attribute in request.POST

    if 'sha1' in request.POST and (should_edit("xform") or "xform" in request.FILES):
        conflict = _get_xform_conflict_response(form, request.POST['sha1'])
        if conflict is not None:
            return conflict

    if should_edit("name"):
        name = request.POST['name']
        form.name[lang] = name
        if not form.form_type == "shadow_form":
            xform = form.wrapped_xform()
            if xform.exists():
                xform.set_name(name)
                save_xform(app, form, xform.render())
        resp['update'] = {'.variable-form_name': trans(form.name, [lang], use_delim=False)}

    if should_edit('comment'):
        form.comment = request.POST['comment']

    if should_edit("name_enum"):
        name_enum = json.loads(request.POST.get("name_enum"))
        form.name_enum = [MappingItem(i) for i in name_enum]

    if should_edit("xform") or "xform" in request.FILES:
        try:
            # support FILES for upload and POST for ajax post from Vellum
            try:
                xform = request.FILES.get('xform').read()
            except Exception:
                xform = request.POST.get('xform')
            else:
                try:
                    xform = six.text_type(xform, encoding="utf-8")
                except Exception:
                    raise Exception("Error uploading form: Please make sure your form is encoded in UTF-8")
            if request.POST.get('cleanup', False):
                try:
                    # First, we strip all newlines and reformat the DOM.
                    px = parseString(xform.replace('\r\n', '')).toprettyxml()
                    # Then we remove excess newlines from the DOM output.
                    text_re = re.compile(r'>\n\s+([^<>\s].*?)\n\s+</', re.DOTALL)
                    prettyXml = text_re.sub(r'>\g<1></', px)
                    xform = prettyXml
                except Exception:
                    pass
            if xform:
                if isinstance(xform, six.text_type):
                    xform = xform.encode('utf-8')
                save_xform(app, form, xform)
            else:
                raise Exception("You didn't select a form to upload")
        except Exception as e:
            notify_exception(request, six.text_type(e))
            if ajax:
                return HttpResponseBadRequest(six.text_type(e))
            else:
                messages.error(request, six.text_type(e))
    if should_edit("references") or should_edit("case_references"):
        form.case_references = _get_case_references(request.POST)
    if should_edit("show_count"):
        show_count = request.POST['show_count']
        form.show_count = True if show_count == "True" else False
    if should_edit("put_in_root"):
        put_in_root = request.POST['put_in_root']
        form.put_in_root = True if put_in_root == "True" else False
    if should_edit('form_filter'):
        form.form_filter = request.POST['form_filter']
    if should_edit('post_form_workflow'):
        form.post_form_workflow = request.POST['post_form_workflow']
    if should_edit('auto_gps_capture'):
        form.auto_gps_capture = request.POST['auto_gps_capture'] == 'true'
    if should_edit('is_release_notes_form'):
        form.is_release_notes_form = request.POST['is_release_notes_form'] == 'true'
    if should_edit('enable_release_notes'):
        form.enable_release_notes = request.POST['enable_release_notes'] == 'true'
        if not form.is_release_notes_form and form.enable_release_notes:
            return json_response(
                {'message': _("You can't enable a form as release notes without allowing it as "
                    "a release notes form <TODO messaging>")},
                status_code=400
            )
    if should_edit('no_vellum'):
        form.no_vellum = request.POST['no_vellum'] == 'true'
    if (should_edit("form_links_xpath_expressions") and
            should_edit("form_links_form_ids") and
            toggles.FORM_LINK_WORKFLOW.enabled(domain)):
        form_links = zip(
            request.POST.getlist('form_links_xpath_expressions'),
            request.POST.getlist('form_links_form_ids'),
            [
                json.loads(datum_json) if datum_json else []
                for datum_json in request.POST.getlist('datums_json')
            ],
        )
        form.form_links = [FormLink(
            xpath=link[0],
            form_id=link[1],
            datums=[
                FormDatum(name=datum['name'], xpath=datum['xpath'])
                for datum in link[2]
            ]
        ) for link in form_links]

    if should_edit('post_form_workflow_fallback'):
        form.post_form_workflow_fallback = request.POST.get('post_form_workflow_fallback')

    if should_edit('custom_instances'):
        instances = json.loads(request.POST.get('custom_instances'))
        try:  # validate that custom instances can be added into the XML
            for instance in instances:
                etree.fromstring(
                    "<instance id='{}' src='{}' />".format(
                        instance.get('instanceId'),
                        instance.get('instancePath')
                    )
                )
        except etree.XMLSyntaxError as error:
            return json_response(
                {'message': _("There was an issue with your custom instances: {}").format(error.message)},
                status_code=400
            )

        form.custom_instances = [
            CustomInstance(
                instance_id=instance.get("instanceId"),
                instance_path=instance.get("instancePath"),
            ) for instance in instances
        ]

    if should_edit('custom_assertions'):
        assertions = json.loads(request.POST.get('custom_assertions'))
        try:  # validate that custom assertions can be added into the XML
            for assertion in assertions:
                etree.fromstring(
                    '<assertion test="{test}"><text><locale id="abc.def"/>{text}</text></assertion>'.format(
                        **assertion
                    )
                )
        except etree.XMLSyntaxError as error:
            return json_response(
                {'message': _("There was an issue with your custom assertions: {}").format(error.message)},
                status_code=400
            )

        existing_assertions = {assertion.test: assertion for assertion in form.custom_assertions}
        new_assertions = []
        for assertion in assertions:
            try:
                new_assertion = existing_assertions[assertion.get('test')]
                new_assertion.text[lang] = assertion.get('text')
            except KeyError:
                new_assertion = CustomAssertion(
                    test=assertion.get('test'),
                    text={lang: assertion.get('text')}
                )
            new_assertions.append(new_assertion)

        form.custom_assertions = new_assertions

    if should_edit("shadow_parent"):
        form.shadow_parent_form_id = request.POST['shadow_parent']

    if should_edit("custom_icon_form"):
        error_message = handle_custom_icon_edits(request, form, lang)
        if error_message:
            return json_response(
                {'message': error_message},
                status_code=400
            )
    handle_media_edits(request, form, should_edit, resp, lang)

    app.save(resp)
    notify_form_changed(domain, request.couch_user, app_id, form_unique_id)
    if ajax:
        return HttpResponse(json.dumps(resp))
    else:
        return back_to_main(request, domain, app_id=app_id, form_unique_id=form_unique_id)
Exemple #48
0
def update_form_translations(sheet, rows, missing_cols, app):
    """
    Modify the translations of a form given a sheet of translation data.
    This does not save the changes to the DB.

    :param sheet: a WorksheetJSONReader
    :param rows: The rows of the sheet (we can't get this from the sheet
    because sheet.__iter__ can only be called once)
    :param missing_cols:
    :param app:
    :return:  Returns a list of message tuples. The first item in each tuple is
    a function like django.contrib.messages.error, and the second is a string.
    """
    msgs = []
    mod_text, form_text = sheet.worksheet.title.split("_")
    module_index = int(mod_text.replace("module", "")) - 1
    form_index = int(form_text.replace("form", "")) - 1
    form = app.get_module(module_index).get_form(form_index)
    if form.source:
        xform = form.wrapped_xform()
    else:
        # This Form doesn't have an xform yet. It is empty.
        # Tell the user this?
        return msgs

    try:
        itext = xform.itext_node
    except XFormException:
        return msgs

    # Make language nodes for each language if they don't yet exist
    #
    # Currently operating under the assumption that every xForm has at least
    # one translation element, that each translation element has a text node
    # for each question and that each text node has a value node under it
    template_translation_el = None
    # Get a translation element to be used as a template for new elements
    for lang in app.langs:
        trans_el = itext.find("./{f}translation[@lang='%s']" % lang)
        if trans_el.exists():
            template_translation_el = trans_el
    assert (template_translation_el is not None)
    # Add missing translation elements
    for lang in app.langs:
        trans_el = itext.find("./{f}translation[@lang='%s']" % lang)
        if not trans_el.exists():
            new_trans_el = copy.deepcopy(template_translation_el.xml)
            new_trans_el.set('lang', lang)
            if lang != app.langs[0]:
                # If the language isn't the default language
                new_trans_el.attrib.pop('default', None)
            else:
                new_trans_el.set('default', '')
            itext.xml.append(new_trans_el)

    # Update the translations
    for lang in app.langs:
        translation_node = itext.find("./{f}translation[@lang='%s']" % lang)
        assert (translation_node.exists())

        for row in rows:
            label_id = row['label']
            text_node = translation_node.find("./{f}text[@id='%s']" % label_id)
            if not text_node.exists():
                msgs.append((
                    messages.warning,
                    "Unrecognized translation label {0} in sheet {1}. That row"
                    " has been skipped".format(label_id,
                                               sheet.worksheet.title)))
                continue

            # Add or remove translations
            for trans_type in ['default', 'audio', 'image', 'video']:

                if trans_type == 'default':
                    attributes = None
                    value_node = next(n
                                      for n in text_node.findall("./{f}value")
                                      if 'form' not in n.attrib)
                else:
                    attributes = {'form': trans_type}
                    value_node = text_node.find("./{f}value[@form='%s']" %
                                                trans_type)

                try:
                    col_key = get_col_key(trans_type, lang)
                    new_translation = row[col_key]
                except KeyError:
                    # error has already been logged as unrecoginzed column
                    continue
                if not new_translation and col_key not in missing_cols:
                    # If the cell corresponding to the label for this question
                    # in this language is empty, fall back to another language
                    for l in app.langs:
                        key = get_col_key(trans_type, l)
                        if key in missing_cols:
                            continue
                        fallback = row[key]
                        if fallback:
                            new_translation = fallback
                            break

                if new_translation:
                    # Create the node if it does not already exist
                    if not value_node.exists():
                        e = etree.Element("{f}value".format(**namespaces),
                                          attributes)
                        text_node.xml.append(e)
                        value_node = WrappedNode(e)
                    # Update the translation
                    value_node.xml.tail = ''
                    for node in value_node.findall("./*"):
                        node.xml.getparent().remove(node.xml)
                    escaped_trans = escape_output_value(new_translation)
                    value_node.xml.text = escaped_trans.text
                    for n in escaped_trans.getchildren():
                        value_node.xml.append(n)
                else:
                    # Remove the node if it already exists
                    if value_node.exists():
                        value_node.xml.getparent().remove(value_node.xml)

    save_xform(app, form, etree.tostring(xform.xml, encoding="unicode"))
    return msgs