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)
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')
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')
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])
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])
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))
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)
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)
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, }
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)
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()
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)
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]
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
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, }
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
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"
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
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
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
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)
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))
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)
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)
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
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
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)
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))
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
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)
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)
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