class Display(OrderedXmlObject): ROOT_NAME = 'display' ORDER = ('text', 'media_image', 'media_audio', 'hint') text = NodeField('text', Text) media_image = StringField('media/@image') media_audio = StringField('media/@audio') hint = NodeField('hint', 'self')
class Detail(IdNode): """ <detail id=""> <title><text/></title> <variables> <__ function=""/> </variables> <field sort=""> <header form="" width=""><text/></header> <template form="" width=""><text/></template> </field> </detail> """ ROOT_NAME = 'detail' title = NodeField('title/text', Text) fields = NodeListField('field', Field) _variables = NodeField('variables', DetailVariableList) def _init_variables(self): if self._variables is None: self._variables = DetailVariableList() def get_variables(self): self._init_variables() return self._variables.variables def set_variables(self, value): self._init_variables() self._variables.variables = value variables = property(get_variables, set_variables)
class DisplayNode(XmlObject): """ Mixin for any node that has the awkward text-or-display subnode, like Command or Menu """ text = NodeField('text', Text) display = NodeField('display', Display) def __init__(self, node=None, context=None, locale_id=None, media_image=None, media_audio=None, **kwargs): super(DisplayNode, self).__init__(node, context, **kwargs) self.set_display( locale_id=locale_id, media_image=media_image, media_audio=media_audio, ) def set_display(self, locale_id=None, media_image=None, media_audio=None): text = Text(locale_id=locale_id) if locale_id else None if media_image or media_audio: self.display = Display( text=text, media_image=media_image, media_audio=media_audio, ) elif text: self.text = text
class TextOrDisplay(XmlObject): text = NodeField('text', Text) display = NodeField('display', LocalizedMediaDisplay) def __init__(self, node=None, context=None, menu_locale_id=None, image_locale_id=None, audio_locale_id=None, media_image=None, media_audio=None, for_action_menu=False, **kwargs): super(TextOrDisplay, self).__init__(node, context, **kwargs) text = Text(locale_id=menu_locale_id) if menu_locale_id else None media_text = [] if media_image: media_text.append(MediaText( locale=LocaleId(locale_id=image_locale_id), form_name='image', )) if media_audio: media_text.append(MediaText( locale=LocaleId(locale_id=audio_locale_id), form_name='audio' )) if media_text: self.display = LocalizedMediaDisplay( media_text=[text] + media_text if text else media_text ) elif for_action_menu and text: self.display = LocalizedMediaDisplay( media_text=[text] ) elif text: self.text = text
class MediaText(XmlObject): ROOT_NAME = 'text' form_name = StringField('@form', choices=['image', 'audio' ]) # Nothing XForm-y about this 'form' locale = NodeField('locale', LocaleId) xpath = NodeField('xpath', Xpath) xpath_function = XPathField('xpath/@function')
class Field(OrderedXmlObject): ROOT_NAME = 'field' ORDER = ('header', 'template', 'sort_node') sort = StringField('@sort') header = NodeField('header', Header) template = NodeField('template', Template) sort_node = NodeField('sort', Sort)
class Annotation(OrderedXmlObject): ORDER = ("x", "y", "text") ROOT_NAME = 'annotation' # TODO: Specify the xpath without specifying "text" for the child (we want the Text class to specify the tag) x = NodeField('x/text', Text) y = NodeField('y/text', Text) text = NodeField('text', Text)
class Field(OrderedXmlObject): ROOT_NAME = 'field' ORDER = ('header', 'template', 'sort_node') sort = StringField('@sort') style = NodeField('style', Style) header = NodeField('header', Header) template = NodeField('template', Template) sort_node = NodeField('sort', Sort) background = NodeField('background/text', Text)
class Entry(XmlObject): ROOT_NAME = 'entry' form = StringField('form') command = NodeField('command', Command) instance = NodeField('instance', Instance) instances = NodeListField('instance', Instance) datums = NodeListField('session/datum', SessionDatum) datum = NodeField('session/datum', SessionDatum)
class Entry(XmlObject): ROOT_NAME = 'entry' form = StringField('form') command = NodeField('command', Command) instances = NodeListField('instance', Instance) datums = NodeListField('session/datum', SessionDatum) stack = NodeField('stack', Stack) assertions = NodeListField('assertions/assert', Assertion)
class Entry(OrderedXmlObject, XmlObject): ROOT_NAME = 'entry' ORDER = ('form', 'command', 'instance', 'datums') form = StringField('form') command = NodeField('command', Command) instances = NodeListField('instance', Instance) datums = NodeListField('session/datum', SessionDatum) queries = NodeListField('session/query', RemoteRequestQuery) session_children = NodeListField('session/*', _wrap_session_datums) all_datums = NodeListField( 'session/*[self::datum or self::instance-datum]', _wrap_session_datums) stack = NodeField('stack', Stack) assertions = NodeListField('assertions/assert', Assertion) def require_instances(self, instances=(), instance_ids=()): used = {(instance.id, instance.src) for instance in self.instances} for instance in instances: if 'remote' in instance.src: continue if (instance.id, instance.src) not in used: self.instances.append( # it's important to make a copy, # since these can't be reused Instance(id=instance.id, src=instance.src)) # make sure the first instance gets inserted # right after the command # once you "suggest" a placement to eulxml, # it'll follow your lead and place the rest of them there too if len(self.instances) == 1: instance_node = self.node.find('instance') command_node = self.node.find('command') self.node.remove(instance_node) self.node.insert( self.node.index(command_node) + 1, instance_node) covered_ids = {instance_id for instance_id, _ in used} for instance_id in instance_ids: if instance_id not in covered_ids: raise UnknownInstanceError( "Instance reference not recognized: {} in XPath \"{}\"" # to get xpath context to show in this error message # make instance_id a unicode subclass with an xpath property .format(instance_id, getattr(instance_id, 'xpath', "(XPath Unknown)"))) sorted_instances = sorted(self.instances, key=lambda instance: instance.id) if sorted_instances != self.instances: self.instances = sorted_instances
class Graph(XmlObject): ROOT_NAME = 'graph' type = StringField("@type", choices=["xy", "bubble", "bar", "time"]) series = NodeListField('series', Series) configuration = NodeField('configuration', ConfigurationGroup) annotations = NodeListField('annotation', Annotation)
class GraphTemplate(Template): # TODO: Is there a way to specify a default/static value for form? form = StringField('@form', choices=['graph']) graph = NodeField('graph', Graph) @classmethod def build(cls, form, graph, locale_config=None, locale_series_config=None, locale_annotation=None): return cls( form=form, graph=Graph( type=graph.graph_type, series=[ Series( nodeset=s.data_path, x_function=s.x_function, y_function=s.y_function, radius_function=s.radius_function, configuration=ConfigurationGroup( configs=( [ # TODO: It might be worth wrapping # these values in quotes (as appropriate) # to prevent the user from having to # figure out why their unquoted colors # aren't working. ConfigurationItem(id=k, xpath_function=v) for k, v in six.iteritems(s.config) ] + [ ConfigurationItem(id=k, locale_id=locale_series_config(index, k)) for k, v in six.iteritems(s.locale_specific_config) ] ) ) ) for index, s in enumerate(graph.series)], configuration=ConfigurationGroup( configs=( [ ConfigurationItem(id=k, xpath_function=v) for k, v in six.iteritems(graph.config) ] + [ ConfigurationItem(id=k, locale_id=locale_config(k)) for k, v in six.iteritems(graph.locale_specific_config) ] ) ), annotations=[ Annotation( x=Text(xpath_function=a.x), y=Text(xpath_function=a.y), text=Text(locale_id=locale_annotation(i)) ) for i, a in enumerate( graph.annotations )] ) )
class ActionMixin(OrderedXmlObject): ROOT_NAME = 'action' ORDER = ('display', 'stack') stack = NodeField('stack', Stack) relevant = XPathField('@relevant') auto_launch = StringField("@auto_launch") redo_last = BooleanField("@redo_last")
class QueryPrompt(DisplayNode): ROOT_NAME = 'prompt' key = StringField('@key') appearance = StringField('@appearance', required=False) input_ = StringField('@input', required=False) itemset = NodeField('itemset', Itemset)
class Entry(LedgerXML): ROOT_NAME = 'entry' id = StringField('@id', required=True) section_id = StringField('@section-id', required=False) quantity = IntegerField('@quantity', required=True) value = NodeField('value', Value, required=False)
class RemoteRequest(OrderedXmlObject, XmlObject): """ Used to set the URL and query details for synchronous search. See "remote-request" in the `CommCare 2.0 Suite Definition`_ for details. .. _CommCare 2.0 Suite Definition: https://github.com/dimagi/commcare/wiki/Suite20#remote-request """ ROOT_NAME = 'remote-request' ORDER = ('post', 'command', 'instances', 'session', 'stack') post = NodeField('post', RemoteRequestPost) instances = NodeListField('instance', Instance) command = NodeField('command', Command) session = NodeField('session', RemoteRequestSession) stack = NodeField('stack', Stack)
class XpathVariable(XmlObject): ROOT_NAME = 'variable' name = StringField('@name') locale_id = StringField('locale/@id') xpath = NodeField('xpath', CalculatedPropertyXpath) @property def value(self): return self.locale_id or self.xpath
class Text(XmlObject): """ <text> <!----------- Exactly one. Will be present wherever text can be defined. Contains a sequential list of string elements to be concatenated to form the text body.--> <xpath function=""> <!------------ 0 or More. An xpath function whose result is a string. References a data model if used in a context where one exists. --> <variable name=""/> <!------------ 0 or More. Variable for the localized string. Variable elements can support any child elements that <body> can. --> </xpath> <locale id=""> <!------------ 0 or More. A localized string. id can be referenced here or as a child--> <id/> <!------------ At Most One. The id of the localized string (if not provided as an attribute --> <argument key=""/> <!------------ 0 or More. Arguments for the localized string. Key is optional. Arguments can support any child elements that <body> can. --> </locale> </text> """ ROOT_NAME = 'text' xpath = NodeField('xpath', Xpath) xpath_function = XPathField('xpath/@function') locale = NodeField('locale', Locale) locale_id = StringField('locale/@id')
class Lookup(OrderedXmlObject): ROOT_NAME = 'lookup' ORDER = ('auto_launch', 'extras', 'responses', 'field') name = StringField("@name") auto_launch = SimpleBooleanField("@auto_launch", "true", "false") action = StringField("@action", required=True) image = StringField("@image") extras = NodeListField('extra', Extra) responses = NodeListField('response', Response) field = NodeField('field', Field)
class Balance(LedgerXML): """https://github.com/dimagi/commcare-core/wiki/ledgerxml#balance-transactions """ ROOT_NAME = REPORT_TYPE_BALANCE ROOT_NS = COMMTRACK_REPORT_XMLNS entity_id = StringField('@entity-id', required=True) date = DateTimeField('@date', required=True) section_id = StringField('@section-id', required=False) entry = NodeField('entry', Entry, required=True)
class DisplayNode(XmlObject): """Any node that has the awkward text-or-display subnode, like Command or Menu""" text = NodeField('text', Text) display = NodeField('display', Display) def __init__(self, locale_id=None, media_image=None, media_audio=None, **kwargs): super(DisplayNode, self).__init__(**kwargs) if locale_id is None: text = None else: text = Text(locale_id=locale_id) if media_image or media_audio: self.display = Display(text=text, media_image=media_image, media_audio=media_audio) else: self.text = text
class OpenRosaResponse(XmlObject): ROOT_NAME = 'OpenRosaResponse' xmlns = StringField('@xmlns') message_nature = StringField('message/@nature') message = StringField('message') auth_keys = NodeField('auth_keys', AuthKeys) def __init__(self, *args, **kwargs): super(OpenRosaResponse, self).__init__(*args, **kwargs) self.xmlns = 'http://openrosa.org/http/response' self.message_nature = 'submit_success'
class Transfer(LedgerXML): """https://github.com/dimagi/commcare-core/wiki/ledgerxml#transfer-transactions """ ROOT_NAME = REPORT_TYPE_TRANSFER ROOT_NS = COMMTRACK_REPORT_XMLNS src = StringField('@src', required=False) dest = StringField('@dest', required=False) date = DateTimeField('@date', required=True) type = StringField('@type', required=False) section_id = StringField('@section-id', required=False) entry = NodeField('entry', Entry, required=True)
class Display(XmlObject): ROOT_NAME = 'display' text = NodeField('text', Text) media_image = StringField('media/@image') media_audio = StringField('media/@audio') def __init__(self, text=None, media_image=None, media_audio=None, **kwargs): super(Display, self).__init__(text=text, **kwargs) self.media_image = media_image self.media_audio = media_audio
class QueryPrompt(DisplayNode): ROOT_NAME = 'prompt' key = StringField('@key') appearance = StringField('@appearance', required=False) receive = StringField('@receive', required=False) hidden = BooleanField('@hidden', required=False) input_ = StringField('@input', required=False) default_value = StringField('@default', required=False) allow_blank_value = BooleanField('@allow_blank_value', required=False) exclude = StringField('@exclude', required=False) required = StringField('@required', required=False) itemset = NodeField('itemset', Itemset)
class Series(OrderedXmlObject): ORDER = ( "configuration", "x_function", "y_function", "radius_function", ) ROOT_NAME = 'series' nodeset = StringField('@nodeset') configuration = NodeField('configuration', ConfigurationGroup) x_function = StringField('x/@function') y_function = StringField('y/@function') radius_function = StringField("radius/@function")
class Detail(IdNode): """ <detail id=""> <title><text/></title> <variables> <__ function=""/> </variables> <field sort=""> <header form="" width=""><text/></header> <template form="" width=""><text/></template> </field> </detail> """ ROOT_NAME = 'detail' title = NodeField('title/text', Text) variables = NodeListField('variables/*', DetailVariable) fields = NodeListField('field', Field)
class ScheduleFixture(Fixture): schedule = NodeField('schedule', Schedule)
class Detail(OrderedXmlObject, IdNode): """ <detail id=""> <title><text/></title> <lookup action="" image="" name=""> <extra key="" value = "" /> <response key ="" /> </lookup> <variables> <__ function=""/> </variables> <field sort=""> <header form="" width=""><text/></header> <template form="" width=""><text/></template> </field> </detail> """ ROOT_NAME = 'detail' ORDER = ('title', 'lookup', 'details', 'fields') nodeset = StringField('@nodeset') print_template = StringField('@print-template') title = NodeField('title/text', Text) lookup = NodeField('lookup', Lookup) fields = NodeListField('field', Field) actions = NodeListField('action', Action) details = NodeListField('detail', "self") _variables = NodeField('variables', DetailVariableList) relevant = StringField('@relevant') def _init_variables(self): if self._variables is None: self._variables = DetailVariableList() def get_variables(self): self._init_variables() return self._variables.variables def set_variables(self, value): self._init_variables() self._variables.variables = value variables = property(get_variables, set_variables) def get_all_xpaths(self): result = set() if self.nodeset: result.add(self.nodeset) if self._variables: for variable in self.variables: result.add(variable.function) if self.actions: for action in self.actions: for frame in action.stack.frames: result.add(frame.if_clause) for datum in getattr(frame, 'datums', []): result.add(datum.value) def _get_graph_config_xpaths(configuration): result = set() for config in configuration.configs: result.add(config.xpath_function) return result for field in self.fields: if field.template.form == 'graph': s = etree.tostring(field.template.node) template = load_xmlobject_from_string(s, xmlclass=GraphTemplate) result.update(_get_graph_config_xpaths(template.graph.configuration)) for series in template.graph.series: result.add(series.nodeset) result.update(_get_graph_config_xpaths(series.configuration)) else: result.add(field.header.text.xpath_function) result.add(field.template.text.xpath_function) for detail in self.details: result.update(detail.get_all_xpaths()) result.discard(None) return result